postgresai 0.17.0-rc.1 → 0.17.0-rc.2
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 +11 -5
- package/dist/bin/postgres-ai.js +187 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -250,7 +250,7 @@ postgresai mon health [--wait <sec>] # Check monitoring services health
|
|
|
250
250
|
|
|
251
251
|
When `--instance-id <uuid>` (or `PGAI_INSTANCE_ID`) is set, `local-install` forwards the id to the platform, which **adopts** the already-provisioned monitoring instance instead of self-registering a duplicate under an auto-created `postgres-ai-monitoring` project. The CLI then persists the adopted instance's real project to `.pgwatch-config`, so checkup reports upload alongside the rest of that instance's health data. Adoption is awaited (with one automatic retry); if it fails, the CLI warns and reports fall back to the default project until you re-run `local-install`. Without the flag, the legacy self-registration path is byte-for-byte unchanged.
|
|
252
252
|
|
|
253
|
-
`local-install` writes `.env` in the monitoring directory. It preserves existing `REPLICATOR_PASSWORD` and `
|
|
253
|
+
`local-install` writes `.env` in the monitoring directory. It preserves existing `REPLICATOR_PASSWORD`, `VM_AUTH_*` and the VictoriaMetrics admin-endpoint keys (`VM_DELETE_AUTH_KEY`, `VM_SNAPSHOT_AUTH_KEY`, `VM_FORCE_MERGE_AUTH_KEY`, `VM_PPROF_AUTH_KEY`, added in 0.17) or generates new random ones when missing. The admin keys live on the `sink-prometheus` command line, so `mon update` and `mon update-config` also recreate that container to apply them; `mon restart` (`docker compose restart`) does not; `VM_AUTH_USERNAME` defaults to `vmauth` when absent. The replication password is used by the demo PostgreSQL standby replication user, and the VM auth credentials are required before Docker Compose can provision Grafana datasources. If you run `docker compose` directly or maintain `.env` yourself, set both VM auth values before upgrading. For rotation, run `VM_AUTH_PASSWORD="$(openssl rand -base64 18)" ./scripts/rotate-vm-auth.sh` from the monitoring directory so `.env`, `sink-prometheus`, and `grafana` update together.
|
|
254
254
|
|
|
255
255
|
#### Monitoring target databases (`mon targets` subgroup)
|
|
256
256
|
```bash
|
|
@@ -301,8 +301,8 @@ run `pgai orgs`) to discover the available ids.
|
|
|
301
301
|
Tools exposed:
|
|
302
302
|
- `list_issues`: returns the same JSON as `postgresai issues list` (args: `{ org_id, status?, hidden_only?, limit?, offset?, debug? }`).
|
|
303
303
|
- `view_issue`: view a single issue with its comments (args: `{ issue_id, org_id, debug? }`).
|
|
304
|
-
- `create_issue`: create a new issue (args: `{ title, description?, org_id, attachments?, debug? }`).
|
|
305
|
-
- `update_issue`: update title/description/status/labels (args: `{ issue_id, org_id, title?, description?, status?, labels?, attachments?, debug? }`).
|
|
304
|
+
- `create_issue`: create a new issue (args: `{ title, description?, org_id, project_id?, labels?, attachments?, is_hidden?, debug? }`).
|
|
305
|
+
- `update_issue`: update title/description/status/labels/is_hidden (args: `{ issue_id, org_id, title?, description?, status?, labels?, attachments?, is_hidden?, debug? }`).
|
|
306
306
|
- `post_issue_comment`: post a comment (args: `{ issue_id, org_id, content?, parent_comment_id?, attachments?, debug? }`).
|
|
307
307
|
- `update_issue_comment`: update an existing comment (args: `{ comment_id, org_id, content?, attachments?, debug? }`).
|
|
308
308
|
- `upload_file`: upload a local file and return the storage URL plus a ready-to-paste markdown link (args: `{ path, org_id, debug? }`).
|
|
@@ -353,8 +353,10 @@ sensitive.
|
|
|
353
353
|
postgresai issues list # List issues (shows: id, title, status, created_at; is_hidden only when set)
|
|
354
354
|
postgresai issues list --hidden-only # Only hidden issues (PostgresAI staff)
|
|
355
355
|
postgresai issues view <issueId> # View issue details and comments
|
|
356
|
-
postgresai issues create --org-id <id>
|
|
356
|
+
postgresai issues create --org-id <id> <title> # Create a new issue
|
|
357
|
+
postgresai issues create --org-id <id> <title> --hidden # Create a hidden issue (PostgresAI staff)
|
|
357
358
|
postgresai issues update <issueId> [--title ... --status ...]# Update an existing issue
|
|
359
|
+
postgresai issues update <issueId> --hidden|--no-hidden # Hide / unhide an issue (PostgresAI staff)
|
|
358
360
|
postgresai issues post-comment <issueId> <content> # Post a comment to an issue
|
|
359
361
|
postgresai issues update-comment <commentId> <content> # Update an existing comment
|
|
360
362
|
postgresai issues files upload <path> # Upload a file, print URL + markdown
|
|
@@ -370,7 +372,11 @@ postgresai issues files download <url> [-o <path>] # Download a file
|
|
|
370
372
|
Hidden issues are staff-internal. `issues list` and `issues view` mark them
|
|
371
373
|
with `is_hidden: true`; the key is omitted entirely otherwise, so ordinary
|
|
372
374
|
issues look exactly as they always have. `--hidden-only` lists just the hidden
|
|
373
|
-
ones, filtered server-side.
|
|
375
|
+
ones, filtered server-side. `issues create --hidden` creates one, and
|
|
376
|
+
`issues update --hidden` / `--no-hidden` hides or unhides an existing issue
|
|
377
|
+
(MCP: `is_hidden` on `create_issue` / `update_issue`). The CLI does no staff
|
|
378
|
+
check of its own: the platform refuses a visibility change for a non-staff
|
|
379
|
+
credential with a plain error, and the CLI prints it and exits non-zero.
|
|
374
380
|
|
|
375
381
|
Staff access is granted per credential, not per person, and a credential that
|
|
376
382
|
does not qualify simply sees nothing — `--hidden-only` returns an empty list
|
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -13524,7 +13524,7 @@ var {
|
|
|
13524
13524
|
// package.json
|
|
13525
13525
|
var package_default = {
|
|
13526
13526
|
name: "postgresai",
|
|
13527
|
-
version: "0.17.0-rc.
|
|
13527
|
+
version: "0.17.0-rc.2",
|
|
13528
13528
|
description: "postgres_ai CLI",
|
|
13529
13529
|
license: "Apache-2.0",
|
|
13530
13530
|
private: false,
|
|
@@ -16884,7 +16884,7 @@ var Result = import_lib.default.Result;
|
|
|
16884
16884
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
16885
16885
|
var defaults = import_lib.default.defaults;
|
|
16886
16886
|
// package.json
|
|
16887
|
-
var version = "0.17.0-rc.
|
|
16887
|
+
var version = "0.17.0-rc.2";
|
|
16888
16888
|
var package_default2 = {
|
|
16889
16889
|
name: "postgresai",
|
|
16890
16890
|
version,
|
|
@@ -17473,7 +17473,7 @@ async function fetchIssue(params) {
|
|
|
17473
17473
|
}
|
|
17474
17474
|
}
|
|
17475
17475
|
async function createIssue(params) {
|
|
17476
|
-
const { apiKey, apiBaseUrl, orgScope, title, orgId, description, projectId, labels, debug } = params;
|
|
17476
|
+
const { apiKey, apiBaseUrl, orgScope, title, orgId, description, projectId, labels, hidden, debug } = params;
|
|
17477
17477
|
if (!apiKey) {
|
|
17478
17478
|
throw new Error("API key is required");
|
|
17479
17479
|
}
|
|
@@ -17498,6 +17498,9 @@ async function createIssue(params) {
|
|
|
17498
17498
|
if (labels && labels.length > 0) {
|
|
17499
17499
|
bodyObj.labels = labels;
|
|
17500
17500
|
}
|
|
17501
|
+
if (hidden === true) {
|
|
17502
|
+
bodyObj.is_hidden = true;
|
|
17503
|
+
}
|
|
17501
17504
|
const body = JSON.stringify(bodyObj);
|
|
17502
17505
|
const headers = issueRequestHeaders(apiKey, orgScope);
|
|
17503
17506
|
if (debug) {
|
|
@@ -17579,15 +17582,15 @@ async function createIssueComment(params) {
|
|
|
17579
17582
|
}
|
|
17580
17583
|
}
|
|
17581
17584
|
async function updateIssue(params) {
|
|
17582
|
-
const { apiKey, apiBaseUrl, orgScope, issueId, title, description, status, labels, debug } = params;
|
|
17585
|
+
const { apiKey, apiBaseUrl, orgScope, issueId, title, description, status, labels, hidden, debug } = params;
|
|
17583
17586
|
if (!apiKey) {
|
|
17584
17587
|
throw new Error("API key is required");
|
|
17585
17588
|
}
|
|
17586
17589
|
if (!issueId) {
|
|
17587
17590
|
throw new Error("issueId is required");
|
|
17588
17591
|
}
|
|
17589
|
-
if (title === undefined && description === undefined && status === undefined && labels === undefined) {
|
|
17590
|
-
throw new Error("At least one field to update is required (title, description, status, or
|
|
17592
|
+
if (title === undefined && description === undefined && status === undefined && labels === undefined && hidden === undefined) {
|
|
17593
|
+
throw new Error("At least one field to update is required (title, description, status, labels, or hidden)");
|
|
17591
17594
|
}
|
|
17592
17595
|
const base = normalizeBaseUrl(apiBaseUrl);
|
|
17593
17596
|
const url = new URL(`${base}/rpc/issue_update`);
|
|
@@ -17606,6 +17609,9 @@ async function updateIssue(params) {
|
|
|
17606
17609
|
if (labels !== undefined) {
|
|
17607
17610
|
bodyObj.p_labels = labels;
|
|
17608
17611
|
}
|
|
17612
|
+
if (hidden !== undefined) {
|
|
17613
|
+
bodyObj.p_is_hidden = hidden;
|
|
17614
|
+
}
|
|
17609
17615
|
const body = JSON.stringify(bodyObj);
|
|
17610
17616
|
const headers = issueRequestHeaders(apiKey, orgScope);
|
|
17611
17617
|
if (debug) {
|
|
@@ -24947,6 +24953,13 @@ var ORG_ID_TOOL_PROPERTY = {
|
|
|
24947
24953
|
description: "Organization ID. Optional with a per-organization token (uses the stored org); REQUIRED with a global token, which can reach every organization the user belongs to and will not assume one. Use orgs_list / `pgai orgs` to discover valid values."
|
|
24948
24954
|
}
|
|
24949
24955
|
};
|
|
24956
|
+
function parseHiddenArg(raw) {
|
|
24957
|
+
if (raw === undefined || raw === null)
|
|
24958
|
+
return {};
|
|
24959
|
+
if (typeof raw === "boolean")
|
|
24960
|
+
return { value: raw };
|
|
24961
|
+
return { error: { content: [{ type: "text", text: "is_hidden must be a boolean" }], isError: true } };
|
|
24962
|
+
}
|
|
24950
24963
|
async function handleToolCall(req, rootOpts, extra) {
|
|
24951
24964
|
const toolName = req.params.name;
|
|
24952
24965
|
const args = req.params.arguments || {};
|
|
@@ -25033,6 +25046,10 @@ async function handleToolCall(req, rootOpts, extra) {
|
|
|
25033
25046
|
let description = rawDescription ? interpretEscapes(rawDescription) : undefined;
|
|
25034
25047
|
const projectId = args.project_id !== undefined ? Number(args.project_id) : undefined;
|
|
25035
25048
|
const labels = Array.isArray(args.labels) ? args.labels.map(String) : undefined;
|
|
25049
|
+
const hiddenArg = parseHiddenArg(args.is_hidden);
|
|
25050
|
+
if (hiddenArg.error)
|
|
25051
|
+
return hiddenArg.error;
|
|
25052
|
+
const hidden = hiddenArg.value === true;
|
|
25036
25053
|
const attachments = Array.isArray(args.attachments) ? args.attachments.map(String).filter((p) => p.length > 0) : [];
|
|
25037
25054
|
const scope = resolveMcpOrgScope(args, apiKey, cfg);
|
|
25038
25055
|
if (scope.error)
|
|
@@ -25046,7 +25063,7 @@ async function handleToolCall(req, rootOpts, extra) {
|
|
|
25046
25063
|
const uploaded = await uploadAttachments({ apiKey, orgScope: scope.orgScope, storageBaseUrl, attachmentPaths: attachments, debug });
|
|
25047
25064
|
description = appendAttachmentsToContent(description ?? "", uploaded);
|
|
25048
25065
|
}
|
|
25049
|
-
const result = await createIssue({ apiKey, apiBaseUrl, title, orgId, orgScope: scope.orgScope, description, projectId, labels, debug });
|
|
25066
|
+
const result = await createIssue({ apiKey, apiBaseUrl, title, orgId, orgScope: scope.orgScope, description, projectId, labels, hidden, debug });
|
|
25050
25067
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
25051
25068
|
}
|
|
25052
25069
|
if (toolName === "update_issue") {
|
|
@@ -25060,9 +25077,13 @@ async function handleToolCall(req, rootOpts, extra) {
|
|
|
25060
25077
|
let description = rawDescription !== undefined ? interpretEscapes(rawDescription) : undefined;
|
|
25061
25078
|
const status = args.status !== undefined ? Number(args.status) : undefined;
|
|
25062
25079
|
const labels = Array.isArray(args.labels) ? args.labels.map(String) : undefined;
|
|
25080
|
+
const hiddenArg = parseHiddenArg(args.is_hidden);
|
|
25081
|
+
if (hiddenArg.error)
|
|
25082
|
+
return hiddenArg.error;
|
|
25083
|
+
const hidden = hiddenArg.value;
|
|
25063
25084
|
const attachments = Array.isArray(args.attachments) ? args.attachments.map(String).filter((p) => p.length > 0) : [];
|
|
25064
|
-
if (title === undefined && description === undefined && status === undefined && labels === undefined && attachments.length === 0) {
|
|
25065
|
-
return { content: [{ type: "text", text: "At least one field to update is required (title, description, status, labels, or attachments)" }], isError: true };
|
|
25085
|
+
if (title === undefined && description === undefined && status === undefined && labels === undefined && hidden === undefined && attachments.length === 0) {
|
|
25086
|
+
return { content: [{ type: "text", text: "At least one field to update is required (title, description, status, labels, is_hidden, or attachments)" }], isError: true };
|
|
25066
25087
|
}
|
|
25067
25088
|
if (status !== undefined && (Number.isNaN(status) || status !== 0 && status !== 1)) {
|
|
25068
25089
|
return { content: [{ type: "text", text: "status must be 0 (open) or 1 (closed)" }], isError: true };
|
|
@@ -25082,7 +25103,7 @@ async function handleToolCall(req, rootOpts, extra) {
|
|
|
25082
25103
|
const uploaded = await uploadAttachments({ apiKey, orgScope: scope.orgScope, storageBaseUrl, attachmentPaths: attachments, debug });
|
|
25083
25104
|
description = appendAttachmentsToContent(description, uploaded);
|
|
25084
25105
|
}
|
|
25085
|
-
const result = await updateIssue({ apiKey, apiBaseUrl, issueId, title, description, status, labels, orgScope: scope.orgScope, debug });
|
|
25106
|
+
const result = await updateIssue({ apiKey, apiBaseUrl, issueId, title, description, status, labels, hidden, orgScope: scope.orgScope, debug });
|
|
25086
25107
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
25087
25108
|
}
|
|
25088
25109
|
if (toolName === "update_issue_comment") {
|
|
@@ -25357,6 +25378,7 @@ async function startMcpServer(rootOpts, extra) {
|
|
|
25357
25378
|
items: { type: "string" },
|
|
25358
25379
|
description: "Local file paths to upload and append as markdown links to the description (images render inline)"
|
|
25359
25380
|
},
|
|
25381
|
+
is_hidden: { type: "boolean", description: "Create as a hidden, staff-internal issue (PostgresAI staff only; same as CLI --hidden)" },
|
|
25360
25382
|
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
25361
25383
|
},
|
|
25362
25384
|
required: ["title"],
|
|
@@ -25365,7 +25387,7 @@ async function startMcpServer(rootOpts, extra) {
|
|
|
25365
25387
|
},
|
|
25366
25388
|
{
|
|
25367
25389
|
name: "update_issue",
|
|
25368
|
-
description: "Update an existing issue (title, description, status, labels). Use status=1 to close, status=0 to reopen. Local files passed via 'attachments' are uploaded and appended to 'description'; if 'description' is omitted, the existing description is fetched first and appended to.",
|
|
25390
|
+
description: "Update an existing issue (title, description, status, labels, is_hidden). Use status=1 to close, status=0 to reopen. Local files passed via 'attachments' are uploaded and appended to 'description'; if 'description' is omitted, the existing description is fetched first and appended to.",
|
|
25369
25391
|
inputSchema: {
|
|
25370
25392
|
type: "object",
|
|
25371
25393
|
properties: {
|
|
@@ -25384,6 +25406,7 @@ async function startMcpServer(rootOpts, extra) {
|
|
|
25384
25406
|
items: { type: "string" },
|
|
25385
25407
|
description: "Local file paths to upload and append as markdown links (images render inline). When provided without 'description', the existing description is fetched and appended to."
|
|
25386
25408
|
},
|
|
25409
|
+
is_hidden: { type: "boolean", description: "true hides the issue, false unhides it (PostgresAI staff only; same as CLI --hidden / --no-hidden). Omit (or pass null) to leave unchanged." },
|
|
25387
25410
|
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
25388
25411
|
},
|
|
25389
25412
|
required: ["issue_id"],
|
|
@@ -25731,7 +25754,7 @@ async function fetchIssue2(params) {
|
|
|
25731
25754
|
}
|
|
25732
25755
|
}
|
|
25733
25756
|
async function createIssue2(params) {
|
|
25734
|
-
const { apiKey, apiBaseUrl, orgScope, title, orgId, description, projectId, labels, debug } = params;
|
|
25757
|
+
const { apiKey, apiBaseUrl, orgScope, title, orgId, description, projectId, labels, hidden, debug } = params;
|
|
25735
25758
|
if (!apiKey) {
|
|
25736
25759
|
throw new Error("API key is required");
|
|
25737
25760
|
}
|
|
@@ -25756,6 +25779,9 @@ async function createIssue2(params) {
|
|
|
25756
25779
|
if (labels && labels.length > 0) {
|
|
25757
25780
|
bodyObj.labels = labels;
|
|
25758
25781
|
}
|
|
25782
|
+
if (hidden === true) {
|
|
25783
|
+
bodyObj.is_hidden = true;
|
|
25784
|
+
}
|
|
25759
25785
|
const body = JSON.stringify(bodyObj);
|
|
25760
25786
|
const headers = issueRequestHeaders2(apiKey, orgScope);
|
|
25761
25787
|
if (debug) {
|
|
@@ -25837,15 +25863,15 @@ async function createIssueComment2(params) {
|
|
|
25837
25863
|
}
|
|
25838
25864
|
}
|
|
25839
25865
|
async function updateIssue2(params) {
|
|
25840
|
-
const { apiKey, apiBaseUrl, orgScope, issueId, title, description, status, labels, debug } = params;
|
|
25866
|
+
const { apiKey, apiBaseUrl, orgScope, issueId, title, description, status, labels, hidden, debug } = params;
|
|
25841
25867
|
if (!apiKey) {
|
|
25842
25868
|
throw new Error("API key is required");
|
|
25843
25869
|
}
|
|
25844
25870
|
if (!issueId) {
|
|
25845
25871
|
throw new Error("issueId is required");
|
|
25846
25872
|
}
|
|
25847
|
-
if (title === undefined && description === undefined && status === undefined && labels === undefined) {
|
|
25848
|
-
throw new Error("At least one field to update is required (title, description, status, or
|
|
25873
|
+
if (title === undefined && description === undefined && status === undefined && labels === undefined && hidden === undefined) {
|
|
25874
|
+
throw new Error("At least one field to update is required (title, description, status, labels, or hidden)");
|
|
25849
25875
|
}
|
|
25850
25876
|
const base = normalizeBaseUrl(apiBaseUrl);
|
|
25851
25877
|
const url = new URL(`${base}/rpc/issue_update`);
|
|
@@ -25864,6 +25890,9 @@ async function updateIssue2(params) {
|
|
|
25864
25890
|
if (labels !== undefined) {
|
|
25865
25891
|
bodyObj.p_labels = labels;
|
|
25866
25892
|
}
|
|
25893
|
+
if (hidden !== undefined) {
|
|
25894
|
+
bodyObj.p_is_hidden = hidden;
|
|
25895
|
+
}
|
|
25867
25896
|
const body = JSON.stringify(bodyObj);
|
|
25868
25897
|
const headers = issueRequestHeaders2(apiKey, orgScope);
|
|
25869
25898
|
if (debug) {
|
|
@@ -38672,47 +38701,101 @@ function stripMatchingQuotes(value) {
|
|
|
38672
38701
|
}
|
|
38673
38702
|
return trimmed;
|
|
38674
38703
|
}
|
|
38704
|
+
var VM_ADMIN_AUTH_KEYS = [
|
|
38705
|
+
"VM_DELETE_AUTH_KEY",
|
|
38706
|
+
"VM_SNAPSHOT_AUTH_KEY",
|
|
38707
|
+
"VM_FORCE_MERGE_AUTH_KEY",
|
|
38708
|
+
"VM_PPROF_AUTH_KEY"
|
|
38709
|
+
];
|
|
38675
38710
|
var REQUIRED_ENV_KEYS = [
|
|
38676
38711
|
{ key: "REPLICATOR_PASSWORD", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.13" },
|
|
38677
38712
|
{ key: "VM_AUTH_USERNAME", defaultValue: () => "vmauth", introducedIn: "0.15" },
|
|
38678
|
-
{ key: "VM_AUTH_PASSWORD", defaultValue: () => crypto2.randomBytes(18).toString("base64"), introducedIn: "0.15" }
|
|
38713
|
+
{ key: "VM_AUTH_PASSWORD", defaultValue: () => crypto2.randomBytes(18).toString("base64"), introducedIn: "0.15" },
|
|
38714
|
+
{ key: "VM_DELETE_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
|
|
38715
|
+
{ key: "VM_SNAPSHOT_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
|
|
38716
|
+
{ key: "VM_FORCE_MERGE_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
|
|
38717
|
+
{ key: "VM_PPROF_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" }
|
|
38679
38718
|
];
|
|
38680
38719
|
function ensureRequiredEnvVars(projectDir) {
|
|
38681
38720
|
const envFile = path7.resolve(projectDir, ".env");
|
|
38682
38721
|
const existing = fs9.existsSync(envFile) ? fs9.readFileSync(envFile, "utf8") : "";
|
|
38683
38722
|
const added = [];
|
|
38684
38723
|
const appendLines = [];
|
|
38724
|
+
let content = existing;
|
|
38685
38725
|
for (const spec of REQUIRED_ENV_KEYS) {
|
|
38686
|
-
const
|
|
38687
|
-
if (!
|
|
38726
|
+
const present = new RegExp(`^[ ]*(?:export[ ]+)?${spec.key}=`, "m");
|
|
38727
|
+
if (!present.test(content)) {
|
|
38688
38728
|
appendLines.push(`${spec.key}=${spec.defaultValue()}`);
|
|
38689
38729
|
added.push(spec.key);
|
|
38730
|
+
continue;
|
|
38731
|
+
}
|
|
38732
|
+
if (VM_ADMIN_AUTH_KEYS.includes(spec.key)) {
|
|
38733
|
+
const blank = new RegExp(`^([ ]*(?:export[ ]+)?${spec.key}=)[ ]*(?:""|'')?[ ]*\r?$`, "gm");
|
|
38734
|
+
if (blank.test(content)) {
|
|
38735
|
+
const value = spec.defaultValue();
|
|
38736
|
+
content = content.replace(blank, (_match, prefix) => `${prefix}${value}`);
|
|
38737
|
+
added.push(spec.key);
|
|
38738
|
+
}
|
|
38690
38739
|
}
|
|
38691
38740
|
}
|
|
38692
|
-
if (appendLines.length === 0) {
|
|
38741
|
+
if (appendLines.length === 0 && content === existing) {
|
|
38693
38742
|
return added;
|
|
38694
38743
|
}
|
|
38695
|
-
const needsTrailingNewline =
|
|
38744
|
+
const needsTrailingNewline = content.length > 0 && !content.endsWith(`
|
|
38696
38745
|
`);
|
|
38697
|
-
const newContent =
|
|
38698
|
-
` : "") + appendLines.join(`
|
|
38746
|
+
const newContent = content + (needsTrailingNewline ? `
|
|
38747
|
+
` : "") + (appendLines.length > 0 ? appendLines.join(`
|
|
38699
38748
|
`) + `
|
|
38700
|
-
|
|
38701
|
-
|
|
38749
|
+
` : "");
|
|
38750
|
+
writeEnvFile(envFile, newContent);
|
|
38702
38751
|
return added;
|
|
38703
38752
|
}
|
|
38753
|
+
function writeEnvFile(envFile, content) {
|
|
38754
|
+
let targetFile = envFile;
|
|
38755
|
+
try {
|
|
38756
|
+
if (fs9.existsSync(envFile))
|
|
38757
|
+
targetFile = fs9.realpathSync(envFile);
|
|
38758
|
+
} catch {}
|
|
38759
|
+
const tmpFile = `${targetFile}.tmp`;
|
|
38760
|
+
fs9.writeFileSync(tmpFile, content, { encoding: "utf8", mode: 384 });
|
|
38761
|
+
try {
|
|
38762
|
+
fs9.chmodSync(tmpFile, 384);
|
|
38763
|
+
if (fs9.existsSync(targetFile)) {
|
|
38764
|
+
const st = fs9.statSync(targetFile);
|
|
38765
|
+
fs9.chownSync(tmpFile, st.uid, st.gid);
|
|
38766
|
+
}
|
|
38767
|
+
} catch {}
|
|
38768
|
+
try {
|
|
38769
|
+
fs9.renameSync(tmpFile, targetFile);
|
|
38770
|
+
} catch (error) {
|
|
38771
|
+
try {
|
|
38772
|
+
fs9.unlinkSync(tmpFile);
|
|
38773
|
+
} catch {}
|
|
38774
|
+
throw error;
|
|
38775
|
+
}
|
|
38776
|
+
}
|
|
38704
38777
|
var LOCAL_INSTALL_MANAGED_ENV_KEYS = [
|
|
38705
38778
|
"PGAI_TAG",
|
|
38706
38779
|
"PGAI_REGISTRY",
|
|
38707
38780
|
"GF_SECURITY_ADMIN_PASSWORD",
|
|
38708
38781
|
"REPLICATOR_PASSWORD",
|
|
38709
38782
|
"VM_AUTH_USERNAME",
|
|
38710
|
-
"VM_AUTH_PASSWORD"
|
|
38783
|
+
"VM_AUTH_PASSWORD",
|
|
38784
|
+
"VM_DELETE_AUTH_KEY",
|
|
38785
|
+
"VM_SNAPSHOT_AUTH_KEY",
|
|
38786
|
+
"VM_FORCE_MERGE_AUTH_KEY",
|
|
38787
|
+
"VM_PPROF_AUTH_KEY"
|
|
38711
38788
|
];
|
|
38712
38789
|
function buildLocalInstallEnv(existingEnv, imageTag) {
|
|
38713
38790
|
const readRaw = (key) => {
|
|
38714
|
-
const
|
|
38715
|
-
|
|
38791
|
+
const re = new RegExp(`^[ ]*(?:export[ ]+)?${key}=(.*)$`, "gm");
|
|
38792
|
+
let value = null;
|
|
38793
|
+
for (const m of existingEnv.matchAll(re)) {
|
|
38794
|
+
value = m[1].trim();
|
|
38795
|
+
}
|
|
38796
|
+
if (value === null)
|
|
38797
|
+
return null;
|
|
38798
|
+
return stripMatchingQuotes(value) === "" ? null : value;
|
|
38716
38799
|
};
|
|
38717
38800
|
const envLines = [`PGAI_TAG=${imageTag}`];
|
|
38718
38801
|
const registry = readRaw("PGAI_REGISTRY");
|
|
@@ -38727,11 +38810,15 @@ function buildLocalInstallEnv(existingEnv, imageTag) {
|
|
|
38727
38810
|
envLines.push(`VM_AUTH_USERNAME=${vmAuthUsername ? stripMatchingQuotes(vmAuthUsername) : "vmauth"}`);
|
|
38728
38811
|
const vmAuthPassword = readRaw("VM_AUTH_PASSWORD");
|
|
38729
38812
|
envLines.push(`VM_AUTH_PASSWORD=${vmAuthPassword ? stripMatchingQuotes(vmAuthPassword) : crypto2.randomBytes(18).toString("base64")}`);
|
|
38813
|
+
for (const key of VM_ADMIN_AUTH_KEYS) {
|
|
38814
|
+
const existing = readRaw(key);
|
|
38815
|
+
envLines.push(`${key}=${existing ? stripMatchingQuotes(existing) : crypto2.randomBytes(32).toString("hex")}`);
|
|
38816
|
+
}
|
|
38730
38817
|
const preservedKeys = [];
|
|
38731
38818
|
for (const line of existingEnv.split(/\r?\n/)) {
|
|
38732
38819
|
if (!line.trim())
|
|
38733
38820
|
continue;
|
|
38734
|
-
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=/);
|
|
38821
|
+
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=/);
|
|
38735
38822
|
if (m) {
|
|
38736
38823
|
if (LOCAL_INSTALL_MANAGED_ENV_KEYS.includes(m[1]))
|
|
38737
38824
|
continue;
|
|
@@ -38743,9 +38830,9 @@ function buildLocalInstallEnv(existingEnv, imageTag) {
|
|
|
38743
38830
|
`) + `
|
|
38744
38831
|
`, preservedKeys };
|
|
38745
38832
|
}
|
|
38746
|
-
async function execFilePromise(file, args) {
|
|
38833
|
+
async function execFilePromise(file, args, options = {}) {
|
|
38747
38834
|
return new Promise((resolve, reject) => {
|
|
38748
|
-
childProcess.execFile(file, args, (error, stdout, stderr) => {
|
|
38835
|
+
childProcess.execFile(file, args, options, (error, stdout, stderr) => {
|
|
38749
38836
|
if (error) {
|
|
38750
38837
|
const err = error;
|
|
38751
38838
|
err.code = typeof error.code === "number" ? error.code : 1;
|
|
@@ -40484,6 +40571,12 @@ function isDockerRunning() {
|
|
|
40484
40571
|
return false;
|
|
40485
40572
|
}
|
|
40486
40573
|
}
|
|
40574
|
+
function shouldScaleOutNodeExporter(args) {
|
|
40575
|
+
const upIndex = args.indexOf("up");
|
|
40576
|
+
if (upIndex === -1)
|
|
40577
|
+
return false;
|
|
40578
|
+
return !args.slice(upIndex + 1).some((a) => !a.startsWith("-"));
|
|
40579
|
+
}
|
|
40487
40580
|
function getComposeCmd() {
|
|
40488
40581
|
const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
|
|
40489
40582
|
if (tryCmd("docker", ["compose", "version"]))
|
|
@@ -40696,7 +40789,7 @@ async function runCompose(args, grafanaPassword) {
|
|
|
40696
40789
|
}
|
|
40697
40790
|
}
|
|
40698
40791
|
const finalArgs = [...args];
|
|
40699
|
-
if (process.platform === "darwin" && args
|
|
40792
|
+
if (process.platform === "darwin" && shouldScaleOutNodeExporter(args)) {
|
|
40700
40793
|
finalArgs.push("--scale", "self-node-exporter=0");
|
|
40701
40794
|
}
|
|
40702
40795
|
return new Promise((resolve) => {
|
|
@@ -40708,6 +40801,51 @@ async function runCompose(args, grafanaPassword) {
|
|
|
40708
40801
|
child.on("close", (code) => resolve(code || 0));
|
|
40709
40802
|
});
|
|
40710
40803
|
}
|
|
40804
|
+
async function applySinkPrometheusConfig(projectDir) {
|
|
40805
|
+
const composeCmd = getComposeCmd();
|
|
40806
|
+
if (!composeCmd) {
|
|
40807
|
+
console.error(`
|
|
40808
|
+
\u2717 docker compose not found, so the sink-prometheus config was not applied.`);
|
|
40809
|
+
return false;
|
|
40810
|
+
}
|
|
40811
|
+
let running;
|
|
40812
|
+
try {
|
|
40813
|
+
const { stdout } = await execFilePromise(composeCmd[0], [
|
|
40814
|
+
...composeCmd.slice(1),
|
|
40815
|
+
"-f",
|
|
40816
|
+
path7.resolve(projectDir, "docker-compose.yml"),
|
|
40817
|
+
"ps",
|
|
40818
|
+
"-q",
|
|
40819
|
+
"sink-prometheus"
|
|
40820
|
+
], { cwd: projectDir });
|
|
40821
|
+
running = stdout.trim().length > 0;
|
|
40822
|
+
} catch (error) {
|
|
40823
|
+
console.error(`
|
|
40824
|
+
\u2717 Could not determine whether sink-prometheus is running.`);
|
|
40825
|
+
console.error(" The admin-endpoint keys may NOT be in effect. To apply them:");
|
|
40826
|
+
console.error(" docker compose up -d --no-deps sink-prometheus");
|
|
40827
|
+
if (process.env.DEBUG) {
|
|
40828
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
40829
|
+
}
|
|
40830
|
+
return false;
|
|
40831
|
+
}
|
|
40832
|
+
if (!running) {
|
|
40833
|
+
console.log(`
|
|
40834
|
+
(sink-prometheus is not running - the new config applies when you start the stack)`);
|
|
40835
|
+
return true;
|
|
40836
|
+
}
|
|
40837
|
+
console.log(`
|
|
40838
|
+
Applying sink-prometheus config (VictoriaMetrics admin keys, #359)...`);
|
|
40839
|
+
const code = await runCompose(["up", "-d", "--no-deps", "sink-prometheus"]);
|
|
40840
|
+
if (code !== 0) {
|
|
40841
|
+
console.error("\u2717 Could not apply the sink-prometheus config.");
|
|
40842
|
+
console.error(" The admin-endpoint keys are NOT in effect until you run:");
|
|
40843
|
+
console.error(" docker compose up -d --no-deps sink-prometheus");
|
|
40844
|
+
return false;
|
|
40845
|
+
}
|
|
40846
|
+
console.log("\u2713 sink-prometheus is running the current config");
|
|
40847
|
+
return true;
|
|
40848
|
+
}
|
|
40711
40849
|
program2.command("help").description("show help").action(() => {
|
|
40712
40850
|
program2.outputHelp();
|
|
40713
40851
|
});
|
|
@@ -40746,7 +40884,7 @@ mon.command("local-install").description("install local monitoring stack (genera
|
|
|
40746
40884
|
const previousTag = previousTagMatch ? previousTagMatch[1].trim() : null;
|
|
40747
40885
|
const imageTag = opts.tag || package_default.version;
|
|
40748
40886
|
const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag);
|
|
40749
|
-
|
|
40887
|
+
writeEnvFile(envFile, envContent);
|
|
40750
40888
|
if (preservedKeys.length > 0) {
|
|
40751
40889
|
console.log(`Preserved existing .env settings: ${preservedKeys.join(", ")}
|
|
40752
40890
|
`);
|
|
@@ -41042,9 +41180,9 @@ Searched: ${demoCandidates.join(", ")}
|
|
|
41042
41180
|
const envLines = envContent.split(/\r?\n/).filter((l) => !/^VM_AUTH_USERNAME=/.test(l) && !/^VM_AUTH_PASSWORD=/.test(l)).filter((l, i, arr) => !(i === arr.length - 1 && l === ""));
|
|
41043
41181
|
envLines.push(`VM_AUTH_USERNAME=${vmAuthUsername}`);
|
|
41044
41182
|
envLines.push(`VM_AUTH_PASSWORD=${vmAuthPassword}`);
|
|
41045
|
-
|
|
41183
|
+
writeEnvFile(envFile, envLines.join(`
|
|
41046
41184
|
`) + `
|
|
41047
|
-
|
|
41185
|
+
`);
|
|
41048
41186
|
}
|
|
41049
41187
|
console.log(`\u2713 VictoriaMetrics auth configured
|
|
41050
41188
|
`);
|
|
@@ -41345,8 +41483,13 @@ mon.command("update-config").description("apply monitoring services configuratio
|
|
|
41345
41483
|
}
|
|
41346
41484
|
await refreshBundledComposeIfStale(projectDir);
|
|
41347
41485
|
const code = await runCompose(["run", "--rm", "sources-generator"]);
|
|
41348
|
-
if (code !== 0)
|
|
41486
|
+
if (code !== 0) {
|
|
41349
41487
|
process.exitCode = code;
|
|
41488
|
+
return;
|
|
41489
|
+
}
|
|
41490
|
+
if (!await applySinkPrometheusConfig(projectDir)) {
|
|
41491
|
+
process.exitCode = 1;
|
|
41492
|
+
}
|
|
41350
41493
|
});
|
|
41351
41494
|
mon.command("update").description("update monitoring stack (migrate .env, pull images)").action(async () => {
|
|
41352
41495
|
console.log(`Updating PostgresAI monitoring stack...
|
|
@@ -41388,6 +41531,10 @@ mon.command("update").description("update monitoring stack (migrate .env, pull i
|
|
|
41388
41531
|
Updating Docker images...`);
|
|
41389
41532
|
const code = await runCompose(["pull"]);
|
|
41390
41533
|
if (code === 0) {
|
|
41534
|
+
if (!await applySinkPrometheusConfig(projectDir)) {
|
|
41535
|
+
process.exitCode = 1;
|
|
41536
|
+
return;
|
|
41537
|
+
}
|
|
41391
41538
|
console.log(`
|
|
41392
41539
|
\u2713 Update completed successfully`);
|
|
41393
41540
|
console.log(`
|
|
@@ -42228,7 +42375,7 @@ withOrgOptions(issues.command("create <title>")).description("create a new issue
|
|
|
42228
42375
|
}, []).option("--attach <path>", "attach a file (uploads to storage and appends a markdown link to the description; repeatable)", (value, previous) => {
|
|
42229
42376
|
previous.push(value);
|
|
42230
42377
|
return previous;
|
|
42231
|
-
}, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
|
|
42378
|
+
}, []).option("--hidden", "create as a hidden issue (PostgresAI staff)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
|
|
42232
42379
|
const rootOpts = program2.opts();
|
|
42233
42380
|
const cfg = readConfig();
|
|
42234
42381
|
const { apiKey } = getConfig(rootOpts);
|
|
@@ -42289,6 +42436,7 @@ withOrgOptions(issues.command("create <title>")).description("create a new issue
|
|
|
42289
42436
|
description: augmentedDescription,
|
|
42290
42437
|
projectId,
|
|
42291
42438
|
labels,
|
|
42439
|
+
hidden: opts.hidden === true,
|
|
42292
42440
|
debug: !!opts.debug
|
|
42293
42441
|
});
|
|
42294
42442
|
spinner.stop();
|
|
@@ -42306,7 +42454,7 @@ withOrgOptions(issues.command("update <issueId>")).description("update an existi
|
|
|
42306
42454
|
}, []).option("--clear-labels", "set labels to an empty list").option("--attach <path>", "attach a file (uploads and appends a markdown link to --description; if --description is omitted the existing description is fetched and appended to; repeatable)", (value, previous) => {
|
|
42307
42455
|
previous.push(value);
|
|
42308
42456
|
return previous;
|
|
42309
|
-
}, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
|
|
42457
|
+
}, []).option("--hidden", "hide the issue (PostgresAI staff)").option("--no-hidden", "unhide the issue (PostgresAI staff)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
|
|
42310
42458
|
const rootOpts = program2.opts();
|
|
42311
42459
|
const cfg = readConfig();
|
|
42312
42460
|
const { apiKey } = getConfig(rootOpts);
|
|
@@ -42377,6 +42525,7 @@ withOrgOptions(issues.command("update <issueId>")).description("update an existi
|
|
|
42377
42525
|
description,
|
|
42378
42526
|
status,
|
|
42379
42527
|
labels,
|
|
42528
|
+
hidden: typeof opts.hidden === "boolean" ? opts.hidden : undefined,
|
|
42380
42529
|
debug: !!opts.debug
|
|
42381
42530
|
});
|
|
42382
42531
|
spinner.stop();
|
|
@@ -43371,5 +43520,6 @@ export {
|
|
|
43371
43520
|
refreshBundledComposeIfStale,
|
|
43372
43521
|
registerMonitoringInstance,
|
|
43373
43522
|
resolveAdoptedProject,
|
|
43523
|
+
shouldScaleOutNodeExporter,
|
|
43374
43524
|
updatePgwatchConfig
|
|
43375
43525
|
};
|