postgresai 0.14.0-dev.71 → 0.14.0-dev.72
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/bin/postgres-ai.ts +376 -93
- package/dist/bin/postgres-ai.js +1063 -139
- package/lib/issues.ts +453 -7
- package/lib/mcp-server.ts +180 -3
- package/lib/metrics-embedded.ts +1 -1
- package/lib/supabase.ts +52 -0
- package/package.json +1 -1
- package/test/init.integration.test.ts +78 -70
- package/test/issues.cli.test.ts +224 -0
- package/test/mcp-server.test.ts +551 -12
package/bin/postgres-ai.ts
CHANGED
|
@@ -10,10 +10,10 @@ import * as os from "os";
|
|
|
10
10
|
import * as crypto from "node:crypto";
|
|
11
11
|
import { Client } from "pg";
|
|
12
12
|
import { startMcpServer } from "../lib/mcp-server";
|
|
13
|
-
import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue, createIssue, updateIssue, updateIssueComment } from "../lib/issues";
|
|
13
|
+
import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue, createIssue, updateIssue, updateIssueComment, fetchActionItem, fetchActionItems, createActionItem, updateActionItem, type ConfigChange } from "../lib/issues";
|
|
14
14
|
import { resolveBaseUrls } from "../lib/util";
|
|
15
15
|
import { applyInitPlan, buildInitPlan, connectWithSslFallback, DEFAULT_MONITORING_USER, KNOWN_PROVIDERS, redactPasswordsInSql, resolveAdminConnection, resolveMonitoringPassword, validateProvider, verifyInitSetup } from "../lib/init";
|
|
16
|
-
import { SupabaseClient, resolveSupabaseConfig, extractProjectRefFromUrl, applyInitPlanViaSupabase, verifyInitSetupViaSupabase, type PgCompatibleError } from "../lib/supabase";
|
|
16
|
+
import { SupabaseClient, resolveSupabaseConfig, extractProjectRefFromUrl, applyInitPlanViaSupabase, verifyInitSetupViaSupabase, fetchPoolerDatabaseUrl, type PgCompatibleError } from "../lib/supabase";
|
|
17
17
|
import * as pkce from "../lib/pkce";
|
|
18
18
|
import * as authServer from "../lib/auth-server";
|
|
19
19
|
import { maskSecret } from "../lib/util";
|
|
@@ -757,6 +757,12 @@ program
|
|
|
757
757
|
|
|
758
758
|
const supabaseClient = new SupabaseClient(supabaseConfig);
|
|
759
759
|
|
|
760
|
+
// Fetch database URL for JSON output (non-blocking, best-effort)
|
|
761
|
+
let databaseUrl: string | null = null;
|
|
762
|
+
if (jsonOutput) {
|
|
763
|
+
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig);
|
|
764
|
+
}
|
|
765
|
+
|
|
760
766
|
try {
|
|
761
767
|
// Get current database name
|
|
762
768
|
const database = await supabaseClient.getCurrentDatabase();
|
|
@@ -776,7 +782,7 @@ program
|
|
|
776
782
|
});
|
|
777
783
|
if (v.ok) {
|
|
778
784
|
if (jsonOutput) {
|
|
779
|
-
|
|
785
|
+
const result: Record<string, unknown> = {
|
|
780
786
|
success: true,
|
|
781
787
|
mode: "supabase",
|
|
782
788
|
action: "verify",
|
|
@@ -784,7 +790,11 @@ program
|
|
|
784
790
|
monitoringUser: opts.monitoringUser,
|
|
785
791
|
verified: true,
|
|
786
792
|
missingOptional: v.missingOptional,
|
|
787
|
-
}
|
|
793
|
+
};
|
|
794
|
+
if (databaseUrl) {
|
|
795
|
+
result.databaseUrl = databaseUrl;
|
|
796
|
+
}
|
|
797
|
+
outputJson(result);
|
|
788
798
|
} else {
|
|
789
799
|
console.log("✓ prepare-db verify: OK");
|
|
790
800
|
if (v.missingOptional.length > 0) {
|
|
@@ -795,7 +805,7 @@ program
|
|
|
795
805
|
return;
|
|
796
806
|
}
|
|
797
807
|
if (jsonOutput) {
|
|
798
|
-
|
|
808
|
+
const result: Record<string, unknown> = {
|
|
799
809
|
success: false,
|
|
800
810
|
mode: "supabase",
|
|
801
811
|
action: "verify",
|
|
@@ -804,7 +814,11 @@ program
|
|
|
804
814
|
verified: false,
|
|
805
815
|
missingRequired: v.missingRequired,
|
|
806
816
|
missingOptional: v.missingOptional,
|
|
807
|
-
}
|
|
817
|
+
};
|
|
818
|
+
if (databaseUrl) {
|
|
819
|
+
result.databaseUrl = databaseUrl;
|
|
820
|
+
}
|
|
821
|
+
outputJson(result);
|
|
808
822
|
} else {
|
|
809
823
|
console.error("✗ prepare-db verify failed: missing required items");
|
|
810
824
|
for (const m of v.missingRequired) console.error(`- ${m}`);
|
|
@@ -909,6 +923,9 @@ program
|
|
|
909
923
|
if (passwordGenerated) {
|
|
910
924
|
result.generatedPassword = monPassword;
|
|
911
925
|
}
|
|
926
|
+
if (databaseUrl) {
|
|
927
|
+
result.databaseUrl = databaseUrl;
|
|
928
|
+
}
|
|
912
929
|
outputJson(result);
|
|
913
930
|
} else {
|
|
914
931
|
console.log(opts.resetPassword ? "✓ prepare-db password reset completed" : "✓ prepare-db completed");
|
|
@@ -2999,22 +3016,44 @@ const issues = program.command("issues").description("issues management");
|
|
|
2999
3016
|
issues
|
|
3000
3017
|
.command("list")
|
|
3001
3018
|
.description("list issues")
|
|
3019
|
+
.option("--status <status>", "filter by status: open, closed, or all (default: all)")
|
|
3020
|
+
.option("--limit <n>", "max number of issues to return (default: 20)", parseInt)
|
|
3021
|
+
.option("--offset <n>", "number of issues to skip (default: 0)", parseInt)
|
|
3002
3022
|
.option("--debug", "enable debug output")
|
|
3003
3023
|
.option("--json", "output raw JSON")
|
|
3004
|
-
.action(async (opts: { debug?: boolean; json?: boolean }) => {
|
|
3024
|
+
.action(async (opts: { status?: string; limit?: number; offset?: number; debug?: boolean; json?: boolean }) => {
|
|
3025
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issues...");
|
|
3005
3026
|
try {
|
|
3006
3027
|
const rootOpts = program.opts<CliOptions>();
|
|
3007
3028
|
const cfg = config.readConfig();
|
|
3008
3029
|
const { apiKey } = getConfig(rootOpts);
|
|
3009
3030
|
if (!apiKey) {
|
|
3031
|
+
spinner.stop();
|
|
3010
3032
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3011
3033
|
process.exitCode = 1;
|
|
3012
3034
|
return;
|
|
3013
3035
|
}
|
|
3036
|
+
const orgId = cfg.orgId ?? undefined;
|
|
3014
3037
|
|
|
3015
3038
|
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
3016
3039
|
|
|
3017
|
-
|
|
3040
|
+
let statusFilter: "open" | "closed" | undefined;
|
|
3041
|
+
if (opts.status === "open") {
|
|
3042
|
+
statusFilter = "open";
|
|
3043
|
+
} else if (opts.status === "closed") {
|
|
3044
|
+
statusFilter = "closed";
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
const result = await fetchIssues({
|
|
3048
|
+
apiKey,
|
|
3049
|
+
apiBaseUrl,
|
|
3050
|
+
orgId,
|
|
3051
|
+
status: statusFilter,
|
|
3052
|
+
limit: opts.limit,
|
|
3053
|
+
offset: opts.offset,
|
|
3054
|
+
debug: !!opts.debug,
|
|
3055
|
+
});
|
|
3056
|
+
spinner.stop();
|
|
3018
3057
|
const trimmed = Array.isArray(result)
|
|
3019
3058
|
? (result as any[]).map((r) => ({
|
|
3020
3059
|
id: (r as any).id,
|
|
@@ -3025,6 +3064,7 @@ issues
|
|
|
3025
3064
|
: result;
|
|
3026
3065
|
printResult(trimmed, opts.json);
|
|
3027
3066
|
} catch (err) {
|
|
3067
|
+
spinner.stop();
|
|
3028
3068
|
const message = err instanceof Error ? err.message : String(err);
|
|
3029
3069
|
console.error(message);
|
|
3030
3070
|
process.exitCode = 1;
|
|
@@ -3037,11 +3077,13 @@ issues
|
|
|
3037
3077
|
.option("--debug", "enable debug output")
|
|
3038
3078
|
.option("--json", "output raw JSON")
|
|
3039
3079
|
.action(async (issueId: string, opts: { debug?: boolean; json?: boolean }) => {
|
|
3080
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issue...");
|
|
3040
3081
|
try {
|
|
3041
3082
|
const rootOpts = program.opts<CliOptions>();
|
|
3042
3083
|
const cfg = config.readConfig();
|
|
3043
3084
|
const { apiKey } = getConfig(rootOpts);
|
|
3044
3085
|
if (!apiKey) {
|
|
3086
|
+
spinner.stop();
|
|
3045
3087
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3046
3088
|
process.exitCode = 1;
|
|
3047
3089
|
return;
|
|
@@ -3051,15 +3093,19 @@ issues
|
|
|
3051
3093
|
|
|
3052
3094
|
const issue = await fetchIssue({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
3053
3095
|
if (!issue) {
|
|
3096
|
+
spinner.stop();
|
|
3054
3097
|
console.error("Issue not found");
|
|
3055
3098
|
process.exitCode = 1;
|
|
3056
3099
|
return;
|
|
3057
3100
|
}
|
|
3058
3101
|
|
|
3102
|
+
spinner.update("Fetching comments...");
|
|
3059
3103
|
const comments = await fetchIssueComments({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
3104
|
+
spinner.stop();
|
|
3060
3105
|
const combined = { issue, comments };
|
|
3061
3106
|
printResult(combined, opts.json);
|
|
3062
3107
|
} catch (err) {
|
|
3108
|
+
spinner.stop();
|
|
3063
3109
|
const message = err instanceof Error ? err.message : String(err);
|
|
3064
3110
|
console.error(message);
|
|
3065
3111
|
process.exitCode = 1;
|
|
@@ -3073,22 +3119,24 @@ issues
|
|
|
3073
3119
|
.option("--debug", "enable debug output")
|
|
3074
3120
|
.option("--json", "output raw JSON")
|
|
3075
3121
|
.action(async (issueId: string, content: string, opts: { parent?: string; debug?: boolean; json?: boolean }) => {
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
}
|
|
3122
|
+
// Interpret escape sequences in content (e.g., \n -> newline)
|
|
3123
|
+
if (opts.debug) {
|
|
3124
|
+
// eslint-disable-next-line no-console
|
|
3125
|
+
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
3126
|
+
}
|
|
3127
|
+
content = interpretEscapes(content);
|
|
3128
|
+
if (opts.debug) {
|
|
3129
|
+
// eslint-disable-next-line no-console
|
|
3130
|
+
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
3131
|
+
}
|
|
3087
3132
|
|
|
3133
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Posting comment...");
|
|
3134
|
+
try {
|
|
3088
3135
|
const rootOpts = program.opts<CliOptions>();
|
|
3089
3136
|
const cfg = config.readConfig();
|
|
3090
3137
|
const { apiKey } = getConfig(rootOpts);
|
|
3091
3138
|
if (!apiKey) {
|
|
3139
|
+
spinner.stop();
|
|
3092
3140
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3093
3141
|
process.exitCode = 1;
|
|
3094
3142
|
return;
|
|
@@ -3104,8 +3152,10 @@ issues
|
|
|
3104
3152
|
parentCommentId: opts.parent,
|
|
3105
3153
|
debug: !!opts.debug,
|
|
3106
3154
|
});
|
|
3155
|
+
spinner.stop();
|
|
3107
3156
|
printResult(result, opts.json);
|
|
3108
3157
|
} catch (err) {
|
|
3158
|
+
spinner.stop();
|
|
3109
3159
|
const message = err instanceof Error ? err.message : String(err);
|
|
3110
3160
|
console.error(message);
|
|
3111
3161
|
process.exitCode = 1;
|
|
@@ -3117,7 +3167,7 @@ issues
|
|
|
3117
3167
|
.description("create a new issue")
|
|
3118
3168
|
.option("--org-id <id>", "organization id (defaults to config orgId)", (v) => parseInt(v, 10))
|
|
3119
3169
|
.option("--project-id <id>", "project id", (v) => parseInt(v, 10))
|
|
3120
|
-
.option("--description <text>", "issue description (
|
|
3170
|
+
.option("--description <text>", "issue description (use \\n for newlines)")
|
|
3121
3171
|
.option(
|
|
3122
3172
|
"--label <label>",
|
|
3123
3173
|
"issue label (repeatable)",
|
|
@@ -3130,34 +3180,35 @@ issues
|
|
|
3130
3180
|
.option("--debug", "enable debug output")
|
|
3131
3181
|
.option("--json", "output raw JSON")
|
|
3132
3182
|
.action(async (rawTitle: string, opts: { orgId?: number; projectId?: number; description?: string; label?: string[]; debug?: boolean; json?: boolean }) => {
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
}
|
|
3183
|
+
const rootOpts = program.opts<CliOptions>();
|
|
3184
|
+
const cfg = config.readConfig();
|
|
3185
|
+
const { apiKey } = getConfig(rootOpts);
|
|
3186
|
+
if (!apiKey) {
|
|
3187
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3188
|
+
process.exitCode = 1;
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3142
3191
|
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3192
|
+
const title = interpretEscapes(String(rawTitle || "").trim());
|
|
3193
|
+
if (!title) {
|
|
3194
|
+
console.error("title is required");
|
|
3195
|
+
process.exitCode = 1;
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3149
3198
|
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3199
|
+
const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
|
|
3200
|
+
if (typeof orgId !== "number") {
|
|
3201
|
+
console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
|
|
3202
|
+
process.exitCode = 1;
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3156
3205
|
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3206
|
+
const description = opts.description !== undefined ? interpretEscapes(String(opts.description)) : undefined;
|
|
3207
|
+
const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
|
|
3208
|
+
const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
|
|
3160
3209
|
|
|
3210
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating issue...");
|
|
3211
|
+
try {
|
|
3161
3212
|
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
3162
3213
|
const result = await createIssue({
|
|
3163
3214
|
apiKey,
|
|
@@ -3169,8 +3220,10 @@ issues
|
|
|
3169
3220
|
labels,
|
|
3170
3221
|
debug: !!opts.debug,
|
|
3171
3222
|
});
|
|
3223
|
+
spinner.stop();
|
|
3172
3224
|
printResult(result, opts.json);
|
|
3173
3225
|
} catch (err) {
|
|
3226
|
+
spinner.stop();
|
|
3174
3227
|
const message = err instanceof Error ? err.message : String(err);
|
|
3175
3228
|
console.error(message);
|
|
3176
3229
|
process.exitCode = 1;
|
|
@@ -3180,8 +3233,8 @@ issues
|
|
|
3180
3233
|
issues
|
|
3181
3234
|
.command("update <issueId>")
|
|
3182
3235
|
.description("update an existing issue (title/description/status/labels)")
|
|
3183
|
-
.option("--title <text>", "new title (
|
|
3184
|
-
.option("--description <text>", "new description (
|
|
3236
|
+
.option("--title <text>", "new title (use \\n for newlines)")
|
|
3237
|
+
.option("--description <text>", "new description (use \\n for newlines)")
|
|
3185
3238
|
.option("--status <value>", "status: open|closed|0|1")
|
|
3186
3239
|
.option(
|
|
3187
3240
|
"--label <label>",
|
|
@@ -3196,49 +3249,50 @@ issues
|
|
|
3196
3249
|
.option("--debug", "enable debug output")
|
|
3197
3250
|
.option("--json", "output raw JSON")
|
|
3198
3251
|
.action(async (issueId: string, opts: { title?: string; description?: string; status?: string; label?: string[]; clearLabels?: boolean; debug?: boolean; json?: boolean }) => {
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
}
|
|
3252
|
+
const rootOpts = program.opts<CliOptions>();
|
|
3253
|
+
const cfg = config.readConfig();
|
|
3254
|
+
const { apiKey } = getConfig(rootOpts);
|
|
3255
|
+
if (!apiKey) {
|
|
3256
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3257
|
+
process.exitCode = 1;
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3208
3260
|
|
|
3209
|
-
|
|
3261
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
3210
3262
|
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
process.exitCode = 1;
|
|
3224
|
-
return;
|
|
3225
|
-
}
|
|
3226
|
-
status = n;
|
|
3227
|
-
}
|
|
3228
|
-
if (status !== 0 && status !== 1) {
|
|
3229
|
-
console.error("status must be 0 (open) or 1 (closed)");
|
|
3263
|
+
const title = opts.title !== undefined ? interpretEscapes(String(opts.title)) : undefined;
|
|
3264
|
+
const description = opts.description !== undefined ? interpretEscapes(String(opts.description)) : undefined;
|
|
3265
|
+
|
|
3266
|
+
let status: number | undefined = undefined;
|
|
3267
|
+
if (opts.status !== undefined) {
|
|
3268
|
+
const raw = String(opts.status).trim().toLowerCase();
|
|
3269
|
+
if (raw === "open") status = 0;
|
|
3270
|
+
else if (raw === "closed") status = 1;
|
|
3271
|
+
else {
|
|
3272
|
+
const n = Number(raw);
|
|
3273
|
+
if (!Number.isFinite(n)) {
|
|
3274
|
+
console.error("status must be open|closed|0|1");
|
|
3230
3275
|
process.exitCode = 1;
|
|
3231
3276
|
return;
|
|
3232
3277
|
}
|
|
3278
|
+
status = n;
|
|
3233
3279
|
}
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
} else if (Array.isArray(opts.label) && opts.label.length > 0) {
|
|
3239
|
-
labels = opts.label.map(String);
|
|
3280
|
+
if (status !== 0 && status !== 1) {
|
|
3281
|
+
console.error("status must be 0 (open) or 1 (closed)");
|
|
3282
|
+
process.exitCode = 1;
|
|
3283
|
+
return;
|
|
3240
3284
|
}
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
let labels: string[] | undefined = undefined;
|
|
3288
|
+
if (opts.clearLabels) {
|
|
3289
|
+
labels = [];
|
|
3290
|
+
} else if (Array.isArray(opts.label) && opts.label.length > 0) {
|
|
3291
|
+
labels = opts.label.map(String);
|
|
3292
|
+
}
|
|
3241
3293
|
|
|
3294
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating issue...");
|
|
3295
|
+
try {
|
|
3242
3296
|
const result = await updateIssue({
|
|
3243
3297
|
apiKey,
|
|
3244
3298
|
apiBaseUrl,
|
|
@@ -3249,8 +3303,10 @@ issues
|
|
|
3249
3303
|
labels,
|
|
3250
3304
|
debug: !!opts.debug,
|
|
3251
3305
|
});
|
|
3306
|
+
spinner.stop();
|
|
3252
3307
|
printResult(result, opts.json);
|
|
3253
3308
|
} catch (err) {
|
|
3309
|
+
spinner.stop();
|
|
3254
3310
|
const message = err instanceof Error ? err.message : String(err);
|
|
3255
3311
|
console.error(message);
|
|
3256
3312
|
process.exitCode = 1;
|
|
@@ -3263,21 +3319,91 @@ issues
|
|
|
3263
3319
|
.option("--debug", "enable debug output")
|
|
3264
3320
|
.option("--json", "output raw JSON")
|
|
3265
3321
|
.action(async (commentId: string, content: string, opts: { debug?: boolean; json?: boolean }) => {
|
|
3322
|
+
if (opts.debug) {
|
|
3323
|
+
// eslint-disable-next-line no-console
|
|
3324
|
+
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
3325
|
+
}
|
|
3326
|
+
content = interpretEscapes(content);
|
|
3327
|
+
if (opts.debug) {
|
|
3328
|
+
// eslint-disable-next-line no-console
|
|
3329
|
+
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
const rootOpts = program.opts<CliOptions>();
|
|
3333
|
+
const cfg = config.readConfig();
|
|
3334
|
+
const { apiKey } = getConfig(rootOpts);
|
|
3335
|
+
if (!apiKey) {
|
|
3336
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3337
|
+
process.exitCode = 1;
|
|
3338
|
+
return;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating comment...");
|
|
3266
3342
|
try {
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3343
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
3344
|
+
|
|
3345
|
+
const result = await updateIssueComment({
|
|
3346
|
+
apiKey,
|
|
3347
|
+
apiBaseUrl,
|
|
3348
|
+
commentId,
|
|
3349
|
+
content,
|
|
3350
|
+
debug: !!opts.debug,
|
|
3351
|
+
});
|
|
3352
|
+
spinner.stop();
|
|
3353
|
+
printResult(result, opts.json);
|
|
3354
|
+
} catch (err) {
|
|
3355
|
+
spinner.stop();
|
|
3356
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3357
|
+
console.error(message);
|
|
3358
|
+
process.exitCode = 1;
|
|
3359
|
+
}
|
|
3360
|
+
});
|
|
3361
|
+
|
|
3362
|
+
// Action Items management (subcommands of issues)
|
|
3363
|
+
issues
|
|
3364
|
+
.command("action-items <issueId>")
|
|
3365
|
+
.description("list action items for an issue")
|
|
3366
|
+
.option("--debug", "enable debug output")
|
|
3367
|
+
.option("--json", "output raw JSON")
|
|
3368
|
+
.action(async (issueId: string, opts: { debug?: boolean; json?: boolean }) => {
|
|
3369
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action items...");
|
|
3370
|
+
try {
|
|
3371
|
+
const rootOpts = program.opts<CliOptions>();
|
|
3372
|
+
const cfg = config.readConfig();
|
|
3373
|
+
const { apiKey } = getConfig(rootOpts);
|
|
3374
|
+
if (!apiKey) {
|
|
3375
|
+
spinner.stop();
|
|
3376
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3377
|
+
process.exitCode = 1;
|
|
3378
|
+
return;
|
|
3275
3379
|
}
|
|
3276
3380
|
|
|
3381
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
3382
|
+
|
|
3383
|
+
const result = await fetchActionItems({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
3384
|
+
spinner.stop();
|
|
3385
|
+
printResult(result, opts.json);
|
|
3386
|
+
} catch (err) {
|
|
3387
|
+
spinner.stop();
|
|
3388
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3389
|
+
console.error(message);
|
|
3390
|
+
process.exitCode = 1;
|
|
3391
|
+
}
|
|
3392
|
+
});
|
|
3393
|
+
|
|
3394
|
+
issues
|
|
3395
|
+
.command("view-action-item <actionItemIds...>")
|
|
3396
|
+
.description("view action item(s) with all details (supports multiple IDs)")
|
|
3397
|
+
.option("--debug", "enable debug output")
|
|
3398
|
+
.option("--json", "output raw JSON")
|
|
3399
|
+
.action(async (actionItemIds: string[], opts: { debug?: boolean; json?: boolean }) => {
|
|
3400
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action item(s)...");
|
|
3401
|
+
try {
|
|
3277
3402
|
const rootOpts = program.opts<CliOptions>();
|
|
3278
3403
|
const cfg = config.readConfig();
|
|
3279
3404
|
const { apiKey } = getConfig(rootOpts);
|
|
3280
3405
|
if (!apiKey) {
|
|
3406
|
+
spinner.stop();
|
|
3281
3407
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3282
3408
|
process.exitCode = 1;
|
|
3283
3409
|
return;
|
|
@@ -3285,15 +3411,172 @@ issues
|
|
|
3285
3411
|
|
|
3286
3412
|
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
3287
3413
|
|
|
3288
|
-
const result = await
|
|
3414
|
+
const result = await fetchActionItem({ apiKey, apiBaseUrl, actionItemIds, debug: !!opts.debug });
|
|
3415
|
+
if (result.length === 0) {
|
|
3416
|
+
spinner.stop();
|
|
3417
|
+
console.error("Action item(s) not found");
|
|
3418
|
+
process.exitCode = 1;
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
spinner.stop();
|
|
3422
|
+
printResult(result, opts.json);
|
|
3423
|
+
} catch (err) {
|
|
3424
|
+
spinner.stop();
|
|
3425
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3426
|
+
console.error(message);
|
|
3427
|
+
process.exitCode = 1;
|
|
3428
|
+
}
|
|
3429
|
+
});
|
|
3430
|
+
|
|
3431
|
+
issues
|
|
3432
|
+
.command("create-action-item <issueId> <title>")
|
|
3433
|
+
.description("create a new action item for an issue")
|
|
3434
|
+
.option("--description <text>", "detailed description (use \\n for newlines)")
|
|
3435
|
+
.option("--sql-action <sql>", "SQL command to execute")
|
|
3436
|
+
.option("--config <json>", "config change as JSON: {\"parameter\":\"...\",\"value\":\"...\"} (repeatable)", (value: string, previous: ConfigChange[]) => {
|
|
3437
|
+
try {
|
|
3438
|
+
previous.push(JSON.parse(value) as ConfigChange);
|
|
3439
|
+
} catch {
|
|
3440
|
+
console.error(`Invalid JSON for --config: ${value}`);
|
|
3441
|
+
process.exit(1);
|
|
3442
|
+
}
|
|
3443
|
+
return previous;
|
|
3444
|
+
}, [] as ConfigChange[])
|
|
3445
|
+
.option("--debug", "enable debug output")
|
|
3446
|
+
.option("--json", "output raw JSON")
|
|
3447
|
+
.action(async (issueId: string, rawTitle: string, opts: { description?: string; sqlAction?: string; config?: ConfigChange[]; debug?: boolean; json?: boolean }) => {
|
|
3448
|
+
const rootOpts = program.opts<CliOptions>();
|
|
3449
|
+
const cfg = config.readConfig();
|
|
3450
|
+
const { apiKey } = getConfig(rootOpts);
|
|
3451
|
+
if (!apiKey) {
|
|
3452
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3453
|
+
process.exitCode = 1;
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
const title = interpretEscapes(String(rawTitle || "").trim());
|
|
3458
|
+
if (!title) {
|
|
3459
|
+
console.error("title is required");
|
|
3460
|
+
process.exitCode = 1;
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
const description = opts.description !== undefined ? interpretEscapes(String(opts.description)) : undefined;
|
|
3465
|
+
const sqlAction = opts.sqlAction;
|
|
3466
|
+
const configs = Array.isArray(opts.config) && opts.config.length > 0 ? opts.config : undefined;
|
|
3467
|
+
|
|
3468
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating action item...");
|
|
3469
|
+
try {
|
|
3470
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
3471
|
+
const result = await createActionItem({
|
|
3289
3472
|
apiKey,
|
|
3290
3473
|
apiBaseUrl,
|
|
3291
|
-
|
|
3292
|
-
|
|
3474
|
+
issueId,
|
|
3475
|
+
title,
|
|
3476
|
+
description,
|
|
3477
|
+
sqlAction,
|
|
3478
|
+
configs,
|
|
3293
3479
|
debug: !!opts.debug,
|
|
3294
3480
|
});
|
|
3295
|
-
|
|
3481
|
+
spinner.stop();
|
|
3482
|
+
printResult({ id: result }, opts.json);
|
|
3296
3483
|
} catch (err) {
|
|
3484
|
+
spinner.stop();
|
|
3485
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3486
|
+
console.error(message);
|
|
3487
|
+
process.exitCode = 1;
|
|
3488
|
+
}
|
|
3489
|
+
});
|
|
3490
|
+
|
|
3491
|
+
issues
|
|
3492
|
+
.command("update-action-item <actionItemId>")
|
|
3493
|
+
.description("update an action item (title, description, status, sql_action, configs)")
|
|
3494
|
+
.option("--title <text>", "new title (use \\n for newlines)")
|
|
3495
|
+
.option("--description <text>", "new description (use \\n for newlines)")
|
|
3496
|
+
.option("--done", "mark as done")
|
|
3497
|
+
.option("--not-done", "mark as not done")
|
|
3498
|
+
.option("--status <value>", "status: waiting_for_approval|approved|rejected")
|
|
3499
|
+
.option("--status-reason <text>", "reason for status change")
|
|
3500
|
+
.option("--sql-action <sql>", "SQL command (use empty string to clear)")
|
|
3501
|
+
.option("--config <json>", "config change as JSON (repeatable, replaces all configs)", (value: string, previous: ConfigChange[]) => {
|
|
3502
|
+
try {
|
|
3503
|
+
previous.push(JSON.parse(value) as ConfigChange);
|
|
3504
|
+
} catch {
|
|
3505
|
+
console.error(`Invalid JSON for --config: ${value}`);
|
|
3506
|
+
process.exit(1);
|
|
3507
|
+
}
|
|
3508
|
+
return previous;
|
|
3509
|
+
}, [] as ConfigChange[])
|
|
3510
|
+
.option("--clear-configs", "clear all config changes")
|
|
3511
|
+
.option("--debug", "enable debug output")
|
|
3512
|
+
.option("--json", "output raw JSON")
|
|
3513
|
+
.action(async (actionItemId: string, opts: { title?: string; description?: string; done?: boolean; notDone?: boolean; status?: string; statusReason?: string; sqlAction?: string; config?: ConfigChange[]; clearConfigs?: boolean; debug?: boolean; json?: boolean }) => {
|
|
3514
|
+
const rootOpts = program.opts<CliOptions>();
|
|
3515
|
+
const cfg = config.readConfig();
|
|
3516
|
+
const { apiKey } = getConfig(rootOpts);
|
|
3517
|
+
if (!apiKey) {
|
|
3518
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
3519
|
+
process.exitCode = 1;
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
const title = opts.title !== undefined ? interpretEscapes(String(opts.title)) : undefined;
|
|
3524
|
+
const description = opts.description !== undefined ? interpretEscapes(String(opts.description)) : undefined;
|
|
3525
|
+
|
|
3526
|
+
let isDone: boolean | undefined = undefined;
|
|
3527
|
+
if (opts.done) isDone = true;
|
|
3528
|
+
else if (opts.notDone) isDone = false;
|
|
3529
|
+
|
|
3530
|
+
let status: string | undefined = undefined;
|
|
3531
|
+
if (opts.status !== undefined) {
|
|
3532
|
+
const validStatuses = ["waiting_for_approval", "approved", "rejected"];
|
|
3533
|
+
if (!validStatuses.includes(opts.status)) {
|
|
3534
|
+
console.error(`status must be one of: ${validStatuses.join(", ")}`);
|
|
3535
|
+
process.exitCode = 1;
|
|
3536
|
+
return;
|
|
3537
|
+
}
|
|
3538
|
+
status = opts.status;
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
const statusReason = opts.statusReason;
|
|
3542
|
+
const sqlAction = opts.sqlAction;
|
|
3543
|
+
|
|
3544
|
+
let configs: ConfigChange[] | undefined = undefined;
|
|
3545
|
+
if (opts.clearConfigs) {
|
|
3546
|
+
configs = [];
|
|
3547
|
+
} else if (Array.isArray(opts.config) && opts.config.length > 0) {
|
|
3548
|
+
configs = opts.config;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
// Check that at least one update field is provided
|
|
3552
|
+
if (title === undefined && description === undefined &&
|
|
3553
|
+
isDone === undefined && status === undefined && statusReason === undefined &&
|
|
3554
|
+
sqlAction === undefined && configs === undefined) {
|
|
3555
|
+
console.error("At least one update option is required");
|
|
3556
|
+
process.exitCode = 1;
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating action item...");
|
|
3561
|
+
try {
|
|
3562
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
3563
|
+
await updateActionItem({
|
|
3564
|
+
apiKey,
|
|
3565
|
+
apiBaseUrl,
|
|
3566
|
+
actionItemId,
|
|
3567
|
+
title,
|
|
3568
|
+
description,
|
|
3569
|
+
isDone,
|
|
3570
|
+
status,
|
|
3571
|
+
statusReason,
|
|
3572
|
+
sqlAction,
|
|
3573
|
+
configs,
|
|
3574
|
+
debug: !!opts.debug,
|
|
3575
|
+
});
|
|
3576
|
+
spinner.stop();
|
|
3577
|
+
printResult({ success: true }, opts.json);
|
|
3578
|
+
} catch (err) {
|
|
3579
|
+
spinner.stop();
|
|
3297
3580
|
const message = err instanceof Error ? err.message : String(err);
|
|
3298
3581
|
console.error(message);
|
|
3299
3582
|
process.exitCode = 1;
|