@commandgarden/cli 0.1.0 → 1.0.0
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/dist/main.js +346 -41
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -7,7 +7,7 @@ var __export = (target, all) => {
|
|
|
7
7
|
|
|
8
8
|
// src/main.ts
|
|
9
9
|
import { Command } from "commander";
|
|
10
|
-
import { join as
|
|
10
|
+
import { join as join3, dirname } from "path";
|
|
11
11
|
import { homedir } from "os";
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
13
|
|
|
@@ -58,6 +58,54 @@ var DaemonClient = class {
|
|
|
58
58
|
}
|
|
59
59
|
return data;
|
|
60
60
|
}
|
|
61
|
+
async connectSSE(path, onEvent, signal) {
|
|
62
|
+
const resp = await this.rawFetch(path, {
|
|
63
|
+
method: "GET",
|
|
64
|
+
headers: {
|
|
65
|
+
"Authorization": `Bearer ${this.token}`,
|
|
66
|
+
"X-CommandGarden": "1",
|
|
67
|
+
"Accept": "text/event-stream"
|
|
68
|
+
},
|
|
69
|
+
signal
|
|
70
|
+
});
|
|
71
|
+
if (!resp.ok || !resp.body) {
|
|
72
|
+
throw new Error(`SSE connection failed: HTTP ${resp.status}`);
|
|
73
|
+
}
|
|
74
|
+
const reader = resp.body.getReader();
|
|
75
|
+
const decoder = new TextDecoder();
|
|
76
|
+
let buffer = "";
|
|
77
|
+
try {
|
|
78
|
+
while (true) {
|
|
79
|
+
const { done, value } = await reader.read();
|
|
80
|
+
if (done) break;
|
|
81
|
+
buffer += decoder.decode(value, { stream: true });
|
|
82
|
+
const lines = buffer.split("\n");
|
|
83
|
+
buffer = lines.pop() ?? "";
|
|
84
|
+
let currentEvent = "message";
|
|
85
|
+
let currentData = "";
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
if (line.startsWith("event: ")) {
|
|
88
|
+
currentEvent = line.slice(7).trim();
|
|
89
|
+
} else if (line.startsWith("data: ")) {
|
|
90
|
+
currentData = line.slice(6);
|
|
91
|
+
} else if (line === "") {
|
|
92
|
+
if (currentData) {
|
|
93
|
+
try {
|
|
94
|
+
onEvent(currentEvent, JSON.parse(currentData));
|
|
95
|
+
} catch {
|
|
96
|
+
onEvent(currentEvent, currentData);
|
|
97
|
+
}
|
|
98
|
+
currentData = "";
|
|
99
|
+
currentEvent = "message";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (signal?.aborted) return;
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
61
109
|
rawFetch(path, init) {
|
|
62
110
|
return fetch(`${this.baseUrl}${path}`, init);
|
|
63
111
|
}
|
|
@@ -79,6 +127,9 @@ function parseDuration(input) {
|
|
|
79
127
|
return new Date(Date.now() - value * UNITS[unit]);
|
|
80
128
|
}
|
|
81
129
|
|
|
130
|
+
// src/commands/run.ts
|
|
131
|
+
import { createInterface } from "readline";
|
|
132
|
+
|
|
82
133
|
// src/formatters.ts
|
|
83
134
|
import Table from "cli-table3";
|
|
84
135
|
function formatTable(data, columns) {
|
|
@@ -138,6 +189,30 @@ function parseConnectorArgs(rawArgs) {
|
|
|
138
189
|
}
|
|
139
190
|
return result;
|
|
140
191
|
}
|
|
192
|
+
function promptApproval(request) {
|
|
193
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
194
|
+
let cancelled = false;
|
|
195
|
+
const cancel = () => {
|
|
196
|
+
cancelled = true;
|
|
197
|
+
rl.close();
|
|
198
|
+
};
|
|
199
|
+
const prompt = `
|
|
200
|
+
\u26A0 Step ${request.stepIndex + 1} [${request.stepType}] in ${request.connectorKey} requires approval.
|
|
201
|
+
Capability: ${request.capability}
|
|
202
|
+
${request.description}
|
|
203
|
+
Approve? (y/n): `;
|
|
204
|
+
const promise = new Promise((resolve) => {
|
|
205
|
+
rl.question(prompt, (answer) => {
|
|
206
|
+
rl.close();
|
|
207
|
+
if (cancelled) return;
|
|
208
|
+
resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
|
|
209
|
+
});
|
|
210
|
+
rl.on("close", () => {
|
|
211
|
+
if (cancelled) resolve(true);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
return { promise, cancel };
|
|
215
|
+
}
|
|
141
216
|
async function executeRun(client, connector, args, outputFormat) {
|
|
142
217
|
try {
|
|
143
218
|
const resp = await client.post("/api/run", {
|
|
@@ -145,10 +220,57 @@ async function executeRun(client, connector, args, outputFormat) {
|
|
|
145
220
|
args,
|
|
146
221
|
format: outputFormat
|
|
147
222
|
});
|
|
148
|
-
if (!resp.
|
|
149
|
-
|
|
223
|
+
if (!resp.requiresApproval) {
|
|
224
|
+
if (!resp.ok) {
|
|
225
|
+
return `Error: ${resp.error ?? "Unknown error from extension"} (${resp.durationMs}ms)`;
|
|
226
|
+
}
|
|
227
|
+
return format(resp.data, resp.columns, connector, outputFormat);
|
|
150
228
|
}
|
|
151
|
-
|
|
229
|
+
const requestId = resp.requestId;
|
|
230
|
+
process.stderr.write(`Pipeline requires approval. Waiting for steps...
|
|
231
|
+
`);
|
|
232
|
+
return new Promise((resolve) => {
|
|
233
|
+
const abortController = new AbortController();
|
|
234
|
+
let activePrompt = null;
|
|
235
|
+
client.connectSSE(
|
|
236
|
+
`/api/run/events/${requestId}`,
|
|
237
|
+
(event, data) => {
|
|
238
|
+
if (event === "approval") {
|
|
239
|
+
const approvalReq = data;
|
|
240
|
+
activePrompt = promptApproval(approvalReq);
|
|
241
|
+
activePrompt.promise.then(async (approved) => {
|
|
242
|
+
activePrompt = null;
|
|
243
|
+
try {
|
|
244
|
+
await client.post("/api/approval", {
|
|
245
|
+
approvalId: approvalReq.approvalId,
|
|
246
|
+
approved
|
|
247
|
+
});
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
if (!approved) {
|
|
251
|
+
abortController.abort();
|
|
252
|
+
resolve(`Aborted: Step ${approvalReq.stepIndex + 1} [${approvalReq.stepType}] was rejected.`);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
} else if (event === "result") {
|
|
256
|
+
if (activePrompt) {
|
|
257
|
+
activePrompt.cancel();
|
|
258
|
+
activePrompt = null;
|
|
259
|
+
process.stderr.write("\n (approved via extension)\n");
|
|
260
|
+
}
|
|
261
|
+
abortController.abort();
|
|
262
|
+
const result = data;
|
|
263
|
+
if (!result.ok) {
|
|
264
|
+
resolve(`Error: ${result.error ?? "Unknown error"} (${result.durationMs}ms)`);
|
|
265
|
+
} else {
|
|
266
|
+
resolve(format(result.data, result.columns ?? [], connector, outputFormat));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
abortController.signal
|
|
271
|
+
).catch(() => {
|
|
272
|
+
});
|
|
273
|
+
});
|
|
152
274
|
} catch (err) {
|
|
153
275
|
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
154
276
|
}
|
|
@@ -4603,6 +4725,7 @@ function buildQueryString(filter) {
|
|
|
4603
4725
|
const params = new URLSearchParams();
|
|
4604
4726
|
if (filter.since) params.set("since", filter.since);
|
|
4605
4727
|
if (filter.connector) params.set("connector", filter.connector);
|
|
4728
|
+
if (filter.type) params.set("type", filter.type);
|
|
4606
4729
|
if (filter.limit) params.set("limit", String(filter.limit));
|
|
4607
4730
|
const qs = params.toString();
|
|
4608
4731
|
return qs ? `?${qs}` : "";
|
|
@@ -4614,7 +4737,7 @@ async function executeAuditList(client, filter) {
|
|
|
4614
4737
|
);
|
|
4615
4738
|
if (resp.events.length === 0) return "No audit events found.";
|
|
4616
4739
|
const table = new Table3({
|
|
4617
|
-
head: ["Timestamp", "Type", "Connector", "User", "Duration", "Rows", "Error"],
|
|
4740
|
+
head: ["Timestamp", "Type", "Connector", "User", "Duration", "Rows", "Source", "Error"],
|
|
4618
4741
|
style: { head: ["cyan"] }
|
|
4619
4742
|
});
|
|
4620
4743
|
for (const e of resp.events) {
|
|
@@ -4625,6 +4748,7 @@ async function executeAuditList(client, filter) {
|
|
|
4625
4748
|
e.user,
|
|
4626
4749
|
`${e.durationMs}ms`,
|
|
4627
4750
|
e.rowCount?.toString() ?? "-",
|
|
4751
|
+
e.source ?? "-",
|
|
4628
4752
|
e.error ?? e.denialReason ?? ""
|
|
4629
4753
|
]);
|
|
4630
4754
|
}
|
|
@@ -4633,7 +4757,24 @@ async function executeAuditList(client, filter) {
|
|
|
4633
4757
|
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
4634
4758
|
}
|
|
4635
4759
|
}
|
|
4636
|
-
var AUDIT_COLUMNS = [
|
|
4760
|
+
var AUDIT_COLUMNS = [
|
|
4761
|
+
"id",
|
|
4762
|
+
"timestamp",
|
|
4763
|
+
"type",
|
|
4764
|
+
"user",
|
|
4765
|
+
"connector",
|
|
4766
|
+
"durationMs",
|
|
4767
|
+
"rowCount",
|
|
4768
|
+
"error",
|
|
4769
|
+
"denialReason",
|
|
4770
|
+
"correlationId",
|
|
4771
|
+
"connectorHash",
|
|
4772
|
+
"source",
|
|
4773
|
+
"args",
|
|
4774
|
+
"domains",
|
|
4775
|
+
"capabilities",
|
|
4776
|
+
"steps"
|
|
4777
|
+
];
|
|
4637
4778
|
async function executeAuditExport(client, filter, format2) {
|
|
4638
4779
|
try {
|
|
4639
4780
|
const resp = await client.get(
|
|
@@ -4646,8 +4787,15 @@ async function executeAuditExport(client, filter, format2) {
|
|
|
4646
4787
|
const rows = resp.events.map(
|
|
4647
4788
|
(e) => AUDIT_COLUMNS.map((col) => {
|
|
4648
4789
|
const val = e[col];
|
|
4649
|
-
|
|
4650
|
-
|
|
4790
|
+
let str;
|
|
4791
|
+
if (val === null || val === void 0) {
|
|
4792
|
+
str = "";
|
|
4793
|
+
} else if (typeof val === "object") {
|
|
4794
|
+
str = JSON.stringify(val);
|
|
4795
|
+
} else {
|
|
4796
|
+
str = String(val);
|
|
4797
|
+
}
|
|
4798
|
+
return str.includes(",") || str.includes('"') || str.includes("\n") ? `"${str.replace(/"/g, '""')}"` : str;
|
|
4651
4799
|
}).join(",")
|
|
4652
4800
|
);
|
|
4653
4801
|
return [header, ...rows].join("\n") + "\n";
|
|
@@ -4655,12 +4803,54 @@ async function executeAuditExport(client, filter, format2) {
|
|
|
4655
4803
|
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
4656
4804
|
}
|
|
4657
4805
|
}
|
|
4806
|
+
async function executeAuditShow(client, id) {
|
|
4807
|
+
try {
|
|
4808
|
+
const resp = await client.get(`/api/audit/${id}`);
|
|
4809
|
+
const e = resp.event;
|
|
4810
|
+
const lines = [
|
|
4811
|
+
`Event: ${e.id}`,
|
|
4812
|
+
`Type: ${e.type}`,
|
|
4813
|
+
`Connector: ${e.connector}${e.connectorHash ? ` (hash: ${e.connectorHash})` : ""}`,
|
|
4814
|
+
`Correlation: ${e.correlationId ?? "-"}`,
|
|
4815
|
+
`User: ${e.user}`,
|
|
4816
|
+
`Timestamp: ${e.timestamp.replace("T", " ").slice(0, 19)}`,
|
|
4817
|
+
`Duration: ${e.durationMs}ms`
|
|
4818
|
+
];
|
|
4819
|
+
if (e.rowCount !== void 0) lines.push(`Rows: ${e.rowCount}`);
|
|
4820
|
+
if (e.error) lines.push(`Error: ${e.error}`);
|
|
4821
|
+
if (e.denialReason) lines.push(`Denial: ${e.denialReason}`);
|
|
4822
|
+
if (e.source) lines.push(`Source: ${e.source}`);
|
|
4823
|
+
if (e.previousValue !== void 0) lines.push(`Previous: ${e.previousValue}`);
|
|
4824
|
+
if (e.newValue !== void 0) lines.push(`New: ${e.newValue}`);
|
|
4825
|
+
if (Object.keys(e.args).length > 0) {
|
|
4826
|
+
lines.push(`Args: ${JSON.stringify(e.args)}`);
|
|
4827
|
+
}
|
|
4828
|
+
if (e.steps && e.steps.length > 0) {
|
|
4829
|
+
lines.push("");
|
|
4830
|
+
lines.push("Pipeline Steps:");
|
|
4831
|
+
const stepTable = new Table3({
|
|
4832
|
+
head: ["#", "Step", "Capability", "Duration", "Error"],
|
|
4833
|
+
style: { head: ["cyan"] }
|
|
4834
|
+
});
|
|
4835
|
+
for (const s of e.steps) {
|
|
4836
|
+
stepTable.push([
|
|
4837
|
+
s.index + 1,
|
|
4838
|
+
s.step,
|
|
4839
|
+
s.capability ?? "-",
|
|
4840
|
+
`${s.durationMs}ms`,
|
|
4841
|
+
s.error ?? ""
|
|
4842
|
+
]);
|
|
4843
|
+
}
|
|
4844
|
+
lines.push(stepTable.toString());
|
|
4845
|
+
}
|
|
4846
|
+
return lines.join("\n");
|
|
4847
|
+
} catch (err) {
|
|
4848
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4658
4851
|
|
|
4659
4852
|
// src/commands/config-cmd.ts
|
|
4660
|
-
import { readFileSync as readFileSync4,
|
|
4661
|
-
import { dirname } from "path";
|
|
4662
|
-
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
4663
|
-
var VALID_SECTIONS = ["daemon", "security", "connectors", "audit", "output"];
|
|
4853
|
+
import { readFileSync as readFileSync4, existsSync as existsSync2 } from "fs";
|
|
4664
4854
|
function executeConfigShow(configPath) {
|
|
4665
4855
|
if (!existsSync2(configPath)) {
|
|
4666
4856
|
return [
|
|
@@ -4671,35 +4861,123 @@ function executeConfigShow(configPath) {
|
|
|
4671
4861
|
}
|
|
4672
4862
|
return readFileSync4(configPath, "utf-8");
|
|
4673
4863
|
}
|
|
4674
|
-
function executeConfigSet(
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
return `
|
|
4678
|
-
}
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4864
|
+
async function executeConfigSet(client, key, value) {
|
|
4865
|
+
try {
|
|
4866
|
+
await client.post("/api/config", { key, value });
|
|
4867
|
+
return `Set ${key} = ${value}`;
|
|
4868
|
+
} catch (err) {
|
|
4869
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
// src/commands/gui-cmd.ts
|
|
4874
|
+
import { spawn as spawn2 } from "child_process";
|
|
4875
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
4876
|
+
import { join as join2 } from "path";
|
|
4877
|
+
import { exec } from "child_process";
|
|
4878
|
+
import { parse as parseYaml2 } from "yaml";
|
|
4879
|
+
function readAppPort(configPath) {
|
|
4880
|
+
try {
|
|
4881
|
+
if (existsSync3(configPath)) {
|
|
4882
|
+
const config2 = parseYaml2(readFileSync5(configPath, "utf-8"));
|
|
4883
|
+
const port = config2?.app?.port;
|
|
4884
|
+
if (typeof port === "number") return port;
|
|
4885
|
+
}
|
|
4886
|
+
} catch {
|
|
4887
|
+
}
|
|
4888
|
+
return 19826;
|
|
4889
|
+
}
|
|
4890
|
+
async function executeGuiStart(baseUrl, cgHome, appScript, opts) {
|
|
4891
|
+
const appPort = opts.configPath ? readAppPort(opts.configPath) : 19826;
|
|
4892
|
+
try {
|
|
4893
|
+
const resp = await fetch(`${baseUrl}/api/status`);
|
|
4894
|
+
if (!resp.ok) throw new Error();
|
|
4895
|
+
} catch {
|
|
4896
|
+
return "Daemon is not running. Start it with: cg daemon start (or use cg up)";
|
|
4897
|
+
}
|
|
4898
|
+
const pidPath = join2(cgHome, "app.pid");
|
|
4899
|
+
if (existsSync3(pidPath)) {
|
|
4900
|
+
const pid = parseInt(readFileSync5(pidPath, "utf-8").trim(), 10);
|
|
4901
|
+
try {
|
|
4902
|
+
process.kill(pid, 0);
|
|
4903
|
+
return "GUI is already running.";
|
|
4904
|
+
} catch {
|
|
4905
|
+
unlinkSync2(pidPath);
|
|
4906
|
+
}
|
|
4907
|
+
}
|
|
4908
|
+
mkdirSync2(cgHome, { recursive: true });
|
|
4909
|
+
if (opts.background) {
|
|
4910
|
+
const child2 = spawn2("node", [appScript], { detached: true, stdio: "ignore" });
|
|
4911
|
+
if (child2.pid) writeFileSync2(pidPath, String(child2.pid));
|
|
4912
|
+
child2.unref();
|
|
4913
|
+
if (!opts.noOpen) openBrowser(`http://127.0.0.1:${appPort}`);
|
|
4914
|
+
return `GUI started (PID: ${child2.pid ?? "unknown"}).`;
|
|
4915
|
+
}
|
|
4916
|
+
if (!opts.noOpen) openBrowser(`http://127.0.0.1:${appPort}`);
|
|
4917
|
+
const child = spawn2("node", [appScript], { stdio: "inherit" });
|
|
4918
|
+
await new Promise((resolve) => child.on("exit", () => resolve()));
|
|
4919
|
+
return "GUI stopped.";
|
|
4920
|
+
}
|
|
4921
|
+
function executeGuiStop(cgHome) {
|
|
4922
|
+
const pidPath = join2(cgHome, "app.pid");
|
|
4923
|
+
if (!existsSync3(pidPath)) return "GUI is not running (no PID file found).";
|
|
4924
|
+
const pid = parseInt(readFileSync5(pidPath, "utf-8").trim(), 10);
|
|
4925
|
+
try {
|
|
4926
|
+
process.kill(pid, "SIGTERM");
|
|
4927
|
+
unlinkSync2(pidPath);
|
|
4928
|
+
return `GUI stopped (PID: ${pid}).`;
|
|
4929
|
+
} catch {
|
|
4930
|
+
unlinkSync2(pidPath);
|
|
4931
|
+
return `GUI process ${pid} not found (stale PID file cleaned up).`;
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
function executeGuiStatus(cgHome) {
|
|
4935
|
+
const pidPath = join2(cgHome, "app.pid");
|
|
4936
|
+
if (!existsSync3(pidPath)) return "GUI: not running (no PID file found).";
|
|
4937
|
+
const pid = parseInt(readFileSync5(pidPath, "utf-8").trim(), 10);
|
|
4938
|
+
try {
|
|
4939
|
+
process.kill(pid, 0);
|
|
4940
|
+
return `GUI: running (PID: ${pid}).`;
|
|
4941
|
+
} catch {
|
|
4942
|
+
return "GUI: not running (stale PID file).";
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
function openBrowser(url) {
|
|
4946
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
4947
|
+
exec(`${cmd} ${url}`);
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4950
|
+
// src/commands/up-down.ts
|
|
4951
|
+
async function executeUp(baseUrl, cgHome, daemonScript, appScript, configPath) {
|
|
4952
|
+
const lines = [];
|
|
4953
|
+
lines.push(await executeDaemonStart(baseUrl, cgHome, daemonScript));
|
|
4954
|
+
for (let i = 0; i < 10; i++) {
|
|
4955
|
+
try {
|
|
4956
|
+
const resp = await fetch(`${baseUrl}/api/status`);
|
|
4957
|
+
if (resp.ok) break;
|
|
4958
|
+
} catch {
|
|
4959
|
+
}
|
|
4960
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
4961
|
+
}
|
|
4962
|
+
lines.push(await executeGuiStart(baseUrl, cgHome, appScript, { background: true, configPath }));
|
|
4963
|
+
return lines.join("\n");
|
|
4964
|
+
}
|
|
4965
|
+
async function executeDown(cgHome) {
|
|
4966
|
+
const lines = [];
|
|
4967
|
+
lines.push(executeGuiStop(cgHome));
|
|
4968
|
+
lines.push(await executeDaemonStop(cgHome));
|
|
4969
|
+
lines.push("All services stopped.");
|
|
4970
|
+
return lines.join("\n");
|
|
4694
4971
|
}
|
|
4695
4972
|
|
|
4696
4973
|
// src/main.ts
|
|
4697
4974
|
var __filename = fileURLToPath(import.meta.url);
|
|
4698
|
-
var __dirname =
|
|
4699
|
-
var DAEMON_SCRIPT =
|
|
4700
|
-
var
|
|
4701
|
-
var
|
|
4702
|
-
var
|
|
4975
|
+
var __dirname = dirname(__filename);
|
|
4976
|
+
var DAEMON_SCRIPT = join3(__dirname, "..", "..", "daemon", "dist", "main.js");
|
|
4977
|
+
var APP_SCRIPT = join3(__dirname, "..", "..", "app", "dist", "server", "main.js");
|
|
4978
|
+
var CG_HOME = join3(homedir(), ".commandgarden");
|
|
4979
|
+
var TOKEN_PATH = join3(CG_HOME, "session-token");
|
|
4980
|
+
var CONFIG_PATH = join3(CG_HOME, "config.yaml");
|
|
4703
4981
|
var BASE_URL = `http://127.0.0.1:19825`;
|
|
4704
4982
|
function createClient() {
|
|
4705
4983
|
const token = readToken(TOKEN_PATH);
|
|
@@ -4739,26 +5017,53 @@ daemon.command("stop").description("Stop the daemon").action(async () => {
|
|
|
4739
5017
|
console.log(await executeDaemonStop(CG_HOME));
|
|
4740
5018
|
});
|
|
4741
5019
|
var audit = program.command("audit").description("View audit event log");
|
|
4742
|
-
audit.command("list").description("List recent audit events").option("--since <duration>", "time window, e.g. 7d, 2w, 12h").option("--connector <pattern>", "filter by connector pattern, e.g. test/*").option("--limit <n>", "max events to return", "100").action(async (opts) => {
|
|
5020
|
+
audit.command("list").description("List recent audit events").option("--since <duration>", "time window, e.g. 7d, 2w, 12h").option("--connector <pattern>", "filter by connector pattern, e.g. test/*").option("--type <pattern>", "filter by event type, e.g. auth.failed, command.*").option("--limit <n>", "max events to return", "100").action(async (opts) => {
|
|
4743
5021
|
const client = createClient();
|
|
4744
5022
|
const filter = {};
|
|
4745
5023
|
if (opts.since) filter.since = parseDuration(opts.since).toISOString();
|
|
4746
5024
|
if (opts.connector) filter.connector = opts.connector;
|
|
5025
|
+
if (opts.type) filter.type = opts.type;
|
|
4747
5026
|
filter.limit = parseInt(opts.limit, 10);
|
|
4748
5027
|
console.log(await executeAuditList(client, filter));
|
|
4749
5028
|
});
|
|
4750
|
-
audit.command("export").description("Export audit events").option("--format <format>", "export format: json, csv", "json").option("--since <duration>", "time window, e.g. 7d, 30d").option("--connector <pattern>", "filter by connector pattern").action(async (opts) => {
|
|
5029
|
+
audit.command("export").description("Export audit events").option("--format <format>", "export format: json, csv", "json").option("--since <duration>", "time window, e.g. 7d, 30d").option("--connector <pattern>", "filter by connector pattern").option("--type <pattern>", "filter by event type").action(async (opts) => {
|
|
4751
5030
|
const client = createClient();
|
|
4752
5031
|
const filter = {};
|
|
4753
5032
|
if (opts.since) filter.since = parseDuration(opts.since).toISOString();
|
|
4754
5033
|
if (opts.connector) filter.connector = opts.connector;
|
|
5034
|
+
if (opts.type) filter.type = opts.type;
|
|
4755
5035
|
console.log(await executeAuditExport(client, filter, opts.format));
|
|
4756
5036
|
});
|
|
5037
|
+
audit.command("show <id>").description("Show full details of an audit event").action(async (id) => {
|
|
5038
|
+
const client = createClient();
|
|
5039
|
+
console.log(await executeAuditShow(client, id));
|
|
5040
|
+
});
|
|
4757
5041
|
var config = program.command("config").description("Manage daemon configuration");
|
|
4758
5042
|
config.command("show").description("Show current configuration").action(() => {
|
|
4759
5043
|
console.log(executeConfigShow(CONFIG_PATH));
|
|
4760
5044
|
});
|
|
4761
|
-
config.command("set <key> <value>").description("Set a configuration value (e.g. daemon.port 9999)").action((key, value) => {
|
|
4762
|
-
|
|
5045
|
+
config.command("set <key> <value>").description("Set a configuration value (e.g. daemon.port 9999)").action(async (key, value) => {
|
|
5046
|
+
const client = createClient();
|
|
5047
|
+
console.log(await executeConfigSet(client, key, value));
|
|
5048
|
+
});
|
|
5049
|
+
var gui = program.command("gui").description("Manage the GUI app server");
|
|
5050
|
+
gui.command("start", { isDefault: true }).description("Start the GUI (foreground by default)").option("-b, --background", "Run in background").option("--no-open", "Do not open browser").action(async (opts) => {
|
|
5051
|
+
console.log(await executeGuiStart(BASE_URL, CG_HOME, APP_SCRIPT, {
|
|
5052
|
+
background: opts.background,
|
|
5053
|
+
noOpen: opts.open === false,
|
|
5054
|
+
configPath: CONFIG_PATH
|
|
5055
|
+
}));
|
|
5056
|
+
});
|
|
5057
|
+
gui.command("stop").description("Stop the GUI").action(() => {
|
|
5058
|
+
console.log(executeGuiStop(CG_HOME));
|
|
5059
|
+
});
|
|
5060
|
+
gui.command("status").description("Check GUI status").action(() => {
|
|
5061
|
+
console.log(executeGuiStatus(CG_HOME));
|
|
5062
|
+
});
|
|
5063
|
+
program.command("up").description("Start daemon + GUI, open browser").action(async () => {
|
|
5064
|
+
console.log(await executeUp(BASE_URL, CG_HOME, DAEMON_SCRIPT, APP_SCRIPT, CONFIG_PATH));
|
|
5065
|
+
});
|
|
5066
|
+
program.command("down").description("Stop GUI + daemon").action(async () => {
|
|
5067
|
+
console.log(await executeDown(CG_HOME));
|
|
4763
5068
|
});
|
|
4764
5069
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commandgarden/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Enterprise browser automation CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "./dist/main.js",
|
|
8
8
|
"bin": {
|
|
9
|
-
"commandgarden": "
|
|
10
|
-
"cg": "
|
|
9
|
+
"commandgarden": "dist/main.js",
|
|
10
|
+
"cg": "dist/main.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|