@commandgarden/cli 2.13.0 → 2.14.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 +58 -28
- package/node_modules/@commandgarden/app/dist/client/index.html +17 -17
- package/node_modules/@commandgarden/app/dist/server/main.js +1 -1
- package/node_modules/@commandgarden/app/dist/server/routes/audit.js +27 -16
- package/node_modules/@commandgarden/app/dist/server/routes/config.js +8 -2
- package/node_modules/@commandgarden/app/dist/server/routes/connectors.js +37 -26
- package/node_modules/@commandgarden/app/dist/server/routes/run.js +3 -4
- package/node_modules/@commandgarden/app/dist/server/routes/status.js +2 -1
- package/node_modules/@commandgarden/app/dist/server/routes/utils.d.ts +2 -0
- package/node_modules/@commandgarden/app/dist/server/routes/utils.js +4 -0
- package/node_modules/@commandgarden/app/dist/server/store.js +53 -53
- package/node_modules/@commandgarden/app/package.json +42 -42
- package/node_modules/@commandgarden/chrome/dist/content-script.js.map +1 -1
- package/node_modules/@commandgarden/chrome/dist/manifest.json +26 -26
- package/node_modules/@commandgarden/chrome/dist/popup.html +218 -218
- package/node_modules/@commandgarden/chrome/dist/popup.js.map +1 -1
- package/node_modules/@commandgarden/chrome/dist/service-worker.js.map +1 -1
- package/node_modules/@commandgarden/chrome/package.json +26 -26
- package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.eval.js +100 -100
- package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.yaml +71 -71
- package/node_modules/@commandgarden/daemon/connectors/gcs-kb-content.yaml +61 -61
- package/node_modules/@commandgarden/daemon/connectors/gcs-kb-pages.yaml +54 -54
- package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.eval.js +115 -115
- package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.yaml +54 -54
- package/node_modules/@commandgarden/daemon/connectors/lib/msal-token.js +20 -20
- package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.eval.js +204 -204
- package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.yaml +42 -42
- package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.eval.js +94 -94
- package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.yaml +34 -34
- package/node_modules/@commandgarden/daemon/connectors/socket-security-news.yaml +63 -63
- package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.eval.js +437 -437
- package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.yaml +53 -53
- package/node_modules/@commandgarden/daemon/connectors/teams-rooms-availability.eval.js +547 -547
- package/node_modules/@commandgarden/daemon/connectors/teams-rooms-availability.yaml +48 -48
- package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.eval.js +52 -52
- package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.yaml +42 -42
- package/node_modules/@commandgarden/daemon/connectors/timetracking-report.eval.js +81 -81
- package/node_modules/@commandgarden/daemon/connectors/timetracking-report.yaml +52 -52
- package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.eval.js +27 -27
- package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.yaml +38 -38
- package/node_modules/@commandgarden/daemon/connectors/tokenmaster-clients-list.yaml +55 -55
- package/node_modules/@commandgarden/daemon/dist/audit-store.js +8 -8
- package/node_modules/@commandgarden/daemon/dist/auth.d.ts +1 -0
- package/node_modules/@commandgarden/daemon/dist/auth.d.ts.map +1 -1
- package/node_modules/@commandgarden/daemon/dist/auth.js +10 -0
- package/node_modules/@commandgarden/daemon/dist/auth.js.map +1 -1
- package/node_modules/@commandgarden/daemon/dist/main.js +12 -5
- package/node_modules/@commandgarden/daemon/dist/main.js.map +1 -1
- package/node_modules/@commandgarden/daemon/dist/server.d.ts.map +1 -1
- package/node_modules/@commandgarden/daemon/dist/server.js +10 -0
- package/node_modules/@commandgarden/daemon/dist/server.js.map +1 -1
- package/node_modules/@commandgarden/daemon/package.json +32 -32
- package/node_modules/@commandgarden/shared/dist/daemon-client.d.ts +5 -1
- package/node_modules/@commandgarden/shared/dist/daemon-client.d.ts.map +1 -1
- package/node_modules/@commandgarden/shared/dist/daemon-client.js +32 -14
- package/node_modules/@commandgarden/shared/dist/daemon-client.js.map +1 -1
- package/node_modules/@commandgarden/shared/package.json +32 -32
- package/package.json +58 -58
- package/node_modules/@commandgarden/app/dist/server/daemon-client.test.js +0 -47
- package/node_modules/@commandgarden/app/dist/server/routes/routes.test.js +0 -137
- package/node_modules/@commandgarden/app/dist/server/store.test.js +0 -59
- package/node_modules/@commandgarden/chrome/dist/fetch-interceptor.js +0 -44
- package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts +0 -24
- package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts.map +0 -1
- package/node_modules/@commandgarden/daemon/dist/cdp-client.js +0 -150
- package/node_modules/@commandgarden/daemon/dist/cdp-client.js.map +0 -1
package/dist/main.js
CHANGED
|
@@ -16534,9 +16534,20 @@ function readToken(tokenPath) {
|
|
|
16534
16534
|
var DaemonClient = class {
|
|
16535
16535
|
baseUrl;
|
|
16536
16536
|
token;
|
|
16537
|
-
|
|
16537
|
+
tokenPath;
|
|
16538
|
+
constructor(baseUrl, token, tokenPath) {
|
|
16538
16539
|
this.baseUrl = baseUrl;
|
|
16539
16540
|
this.token = token;
|
|
16541
|
+
this.tokenPath = tokenPath;
|
|
16542
|
+
}
|
|
16543
|
+
tryRefreshToken() {
|
|
16544
|
+
if (!this.tokenPath)
|
|
16545
|
+
return false;
|
|
16546
|
+
const fresh = readToken(this.tokenPath);
|
|
16547
|
+
if (!fresh || fresh === this.token)
|
|
16548
|
+
return false;
|
|
16549
|
+
this.token = fresh;
|
|
16550
|
+
return true;
|
|
16540
16551
|
}
|
|
16541
16552
|
async get(path) {
|
|
16542
16553
|
return this.request("GET", path);
|
|
@@ -16558,20 +16569,20 @@ var DaemonClient = class {
|
|
|
16558
16569
|
return data;
|
|
16559
16570
|
}
|
|
16560
16571
|
async pipeRaw(path) {
|
|
16561
|
-
const resp = await this.
|
|
16572
|
+
const resp = await this.fetchWithRetry(path, () => ({
|
|
16562
16573
|
method: "GET",
|
|
16563
16574
|
headers: this.sseHeaders()
|
|
16564
|
-
});
|
|
16575
|
+
}));
|
|
16565
16576
|
if (!resp.ok)
|
|
16566
16577
|
throw new Error(`SSE connection failed: HTTP ${resp.status}`);
|
|
16567
16578
|
return resp;
|
|
16568
16579
|
}
|
|
16569
16580
|
async connectSSE(path, onEvent, signal) {
|
|
16570
|
-
const resp = await this.
|
|
16581
|
+
const resp = await this.fetchWithRetry(path, () => ({
|
|
16571
16582
|
method: "GET",
|
|
16572
16583
|
headers: this.sseHeaders(),
|
|
16573
16584
|
signal
|
|
16574
|
-
});
|
|
16585
|
+
}));
|
|
16575
16586
|
if (!resp.ok || !resp.body) {
|
|
16576
16587
|
throw new Error(`SSE connection failed: HTTP ${resp.status}`);
|
|
16577
16588
|
}
|
|
@@ -16619,18 +16630,18 @@ var DaemonClient = class {
|
|
|
16619
16630
|
"Accept": "text/event-stream"
|
|
16620
16631
|
};
|
|
16621
16632
|
}
|
|
16633
|
+
authHeaders() {
|
|
16634
|
+
return {
|
|
16635
|
+
"Authorization": `Bearer ${this.token}`,
|
|
16636
|
+
"X-CommandGarden": "1",
|
|
16637
|
+
"Content-Type": "application/json"
|
|
16638
|
+
};
|
|
16639
|
+
}
|
|
16622
16640
|
async request(method, path, body) {
|
|
16641
|
+
const jsonBody = body ? JSON.stringify(body) : void 0;
|
|
16623
16642
|
let resp;
|
|
16624
16643
|
try {
|
|
16625
|
-
resp = await this.
|
|
16626
|
-
method,
|
|
16627
|
-
headers: {
|
|
16628
|
-
"Authorization": `Bearer ${this.token}`,
|
|
16629
|
-
"X-CommandGarden": "1",
|
|
16630
|
-
"Content-Type": "application/json"
|
|
16631
|
-
},
|
|
16632
|
-
body: body ? JSON.stringify(body) : void 0
|
|
16633
|
-
});
|
|
16644
|
+
resp = await this.fetchWithRetry(path, () => ({ method, headers: this.authHeaders(), body: jsonBody }));
|
|
16634
16645
|
} catch {
|
|
16635
16646
|
throw new Error("Cannot connect to daemon. Is it running? Try: cg daemon start");
|
|
16636
16647
|
}
|
|
@@ -16640,6 +16651,13 @@ var DaemonClient = class {
|
|
|
16640
16651
|
}
|
|
16641
16652
|
return data;
|
|
16642
16653
|
}
|
|
16654
|
+
async fetchWithRetry(path, initFn) {
|
|
16655
|
+
let resp = await this.rawFetch(path, initFn());
|
|
16656
|
+
if (resp.status === 401 && this.tryRefreshToken()) {
|
|
16657
|
+
resp = await this.rawFetch(path, initFn());
|
|
16658
|
+
}
|
|
16659
|
+
return resp;
|
|
16660
|
+
}
|
|
16643
16661
|
rawFetch(path, init) {
|
|
16644
16662
|
return fetch(`${this.baseUrl}${path}`, init);
|
|
16645
16663
|
}
|
|
@@ -17009,7 +17027,7 @@ function executeValidate(filePath) {
|
|
|
17009
17027
|
|
|
17010
17028
|
// src/commands/daemon-cmd.ts
|
|
17011
17029
|
import { spawn } from "child_process";
|
|
17012
|
-
import { readFileSync as readFileSync5,
|
|
17030
|
+
import { readFileSync as readFileSync5, existsSync as existsSync3, unlinkSync as unlinkSync2, mkdirSync, openSync, closeSync } from "fs";
|
|
17013
17031
|
import { join as join2 } from "path";
|
|
17014
17032
|
|
|
17015
17033
|
// src/poll.ts
|
|
@@ -17089,7 +17107,6 @@ async function executeDaemonStart(baseUrl, cgHome, daemonScript) {
|
|
|
17089
17107
|
child.on("error", () => {
|
|
17090
17108
|
spawnFailed = true;
|
|
17091
17109
|
});
|
|
17092
|
-
if (child.pid) writeFileSync2(pidPath, String(child.pid));
|
|
17093
17110
|
child.unref();
|
|
17094
17111
|
const outcome = await pollUntilReady({
|
|
17095
17112
|
checkReady: async () => {
|
|
@@ -17130,20 +17147,33 @@ function readLogTail(logPath, lines) {
|
|
|
17130
17147
|
return "(no log available)";
|
|
17131
17148
|
}
|
|
17132
17149
|
}
|
|
17133
|
-
async function executeDaemonStop(cgHome) {
|
|
17150
|
+
async function executeDaemonStop(cgHome, baseUrl = "http://127.0.0.1:9091") {
|
|
17134
17151
|
const pidPath = join2(cgHome, "daemon.pid");
|
|
17135
|
-
if (
|
|
17136
|
-
|
|
17152
|
+
if (existsSync3(pidPath)) {
|
|
17153
|
+
const pid = parseInt(readFileSync5(pidPath, "utf-8").trim(), 10);
|
|
17154
|
+
try {
|
|
17155
|
+
process.kill(pid, "SIGTERM");
|
|
17156
|
+
unlinkSync2(pidPath);
|
|
17157
|
+
return `Daemon stopped (PID: ${pid}).`;
|
|
17158
|
+
} catch {
|
|
17159
|
+
unlinkSync2(pidPath);
|
|
17160
|
+
return `Daemon process ${pid} not found (stale PID file cleaned up).`;
|
|
17161
|
+
}
|
|
17137
17162
|
}
|
|
17138
|
-
const pid = parseInt(readFileSync5(pidPath, "utf-8").trim(), 10);
|
|
17139
17163
|
try {
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17164
|
+
const tokenPath = join2(cgHome, "session-token");
|
|
17165
|
+
const token = existsSync3(tokenPath) ? readFileSync5(tokenPath, "utf-8").trim() : null;
|
|
17166
|
+
if (token) {
|
|
17167
|
+
const resp = await fetch(`${baseUrl}/api/shutdown`, {
|
|
17168
|
+
method: "POST",
|
|
17169
|
+
headers: { "Authorization": `Bearer ${token}`, "X-CommandGarden": "1", "Content-Type": "application/json" },
|
|
17170
|
+
body: JSON.stringify({ confirm: true })
|
|
17171
|
+
});
|
|
17172
|
+
if (resp.ok) return "Daemon stopped via API (PID file was missing).";
|
|
17173
|
+
}
|
|
17143
17174
|
} catch {
|
|
17144
|
-
unlinkSync2(pidPath);
|
|
17145
|
-
return `Daemon process ${pid} not found (stale PID file cleaned up).`;
|
|
17146
17175
|
}
|
|
17176
|
+
return "Daemon is not running (no PID file found).";
|
|
17147
17177
|
}
|
|
17148
17178
|
|
|
17149
17179
|
// src/commands/audit.ts
|
|
@@ -17294,7 +17324,7 @@ async function executeConfigSet(client, key, value) {
|
|
|
17294
17324
|
// src/commands/gui-cmd.ts
|
|
17295
17325
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
17296
17326
|
import { spawn as spawn2 } from "child_process";
|
|
17297
|
-
import { readFileSync as readFileSync7, writeFileSync as
|
|
17327
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync5, unlinkSync as unlinkSync3, mkdirSync as mkdirSync2, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
17298
17328
|
import { join as join3 } from "path";
|
|
17299
17329
|
function readAppPort(configPath) {
|
|
17300
17330
|
try {
|
|
@@ -17343,7 +17373,7 @@ async function executeGuiStart(baseUrl, cgHome, appScript, opts) {
|
|
|
17343
17373
|
child2.on("error", () => {
|
|
17344
17374
|
spawnFailed = true;
|
|
17345
17375
|
});
|
|
17346
|
-
if (child2.pid)
|
|
17376
|
+
if (child2.pid) writeFileSync2(pidPath, String(child2.pid));
|
|
17347
17377
|
child2.unref();
|
|
17348
17378
|
if (opts.noOpen) {
|
|
17349
17379
|
return { status: "started", message: `GUI started (PID: ${child2.pid ?? "unknown"}).`, pid: child2.pid };
|
|
@@ -17430,7 +17460,7 @@ function openBrowser(url, cgHome) {
|
|
|
17430
17460
|
const logPath = join3(cgHome, "browser.log");
|
|
17431
17461
|
const log = (line) => {
|
|
17432
17462
|
try {
|
|
17433
|
-
|
|
17463
|
+
writeFileSync2(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${line}
|
|
17434
17464
|
`, { flag: "a" });
|
|
17435
17465
|
} catch {
|
|
17436
17466
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" data-theme="dark">
|
|
3
|
-
<head>
|
|
4
|
-
<script>
|
|
5
|
-
(function(){var t=localStorage.getItem('theme');if(t){document.documentElement.setAttribute('data-theme',t)}else if(matchMedia('(prefers-color-scheme:dark)').matches){document.documentElement.setAttribute('data-theme','dark')}})();
|
|
6
|
-
</script>
|
|
7
|
-
<meta charset="UTF-8" />
|
|
8
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
9
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
-
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
12
|
-
<title>commandGarden</title>
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<script>
|
|
5
|
+
(function(){var t=localStorage.getItem('theme');if(t){document.documentElement.setAttribute('data-theme',t)}else if(matchMedia('(prefers-color-scheme:dark)').matches){document.documentElement.setAttribute('data-theme','dark')}})();
|
|
6
|
+
</script>
|
|
7
|
+
<meta charset="UTF-8" />
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
12
|
+
<title>commandGarden</title>
|
|
13
13
|
<script type="module" crossorigin src="/assets/index-C8yqRQk-.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/index-rHqHbsBw.css">
|
|
15
|
-
</head>
|
|
16
|
-
<body>
|
|
17
|
-
<div id="root"></div>
|
|
18
|
-
</body>
|
|
19
|
-
</html>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div id="root"></div>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
@@ -33,7 +33,7 @@ async function start() {
|
|
|
33
33
|
}
|
|
34
34
|
const port = readAppPort();
|
|
35
35
|
const daemonUrl = 'http://127.0.0.1:9091';
|
|
36
|
-
const daemon = new DaemonClient(daemonUrl, token);
|
|
36
|
+
const daemon = new DaemonClient(daemonUrl, token, TOKEN_PATH);
|
|
37
37
|
const store = await AppStore.create(join(CG_HOME, 'app.db'));
|
|
38
38
|
const app = Fastify();
|
|
39
39
|
registerRoutes(app, daemon, store);
|
|
@@ -1,20 +1,31 @@
|
|
|
1
|
+
import { daemonErrorMessage } from './utils.js';
|
|
1
2
|
export function auditRoutes(app, daemon) {
|
|
2
|
-
app.get('/api/audit', async (req) => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
app.get('/api/audit', async (req, reply) => {
|
|
4
|
+
try {
|
|
5
|
+
const query = req.query;
|
|
6
|
+
const params = new URLSearchParams();
|
|
7
|
+
if (query.since)
|
|
8
|
+
params.set('since', query.since);
|
|
9
|
+
if (query.connector)
|
|
10
|
+
params.set('connector', query.connector);
|
|
11
|
+
if (query.type)
|
|
12
|
+
params.set('type', query.type);
|
|
13
|
+
if (query.limit)
|
|
14
|
+
params.set('limit', query.limit);
|
|
15
|
+
const qs = params.toString();
|
|
16
|
+
return await daemon.get(`/api/audit${qs ? `?${qs}` : ''}`);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
reply.code(502).send({ ok: false, error: daemonErrorMessage(err) });
|
|
20
|
+
}
|
|
15
21
|
});
|
|
16
|
-
app.get('/api/audit/:id', async (req) => {
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
app.get('/api/audit/:id', async (req, reply) => {
|
|
23
|
+
try {
|
|
24
|
+
const { id } = req.params;
|
|
25
|
+
return await daemon.get(`/api/audit/${id}`);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
reply.code(502).send({ ok: false, error: daemonErrorMessage(err) });
|
|
29
|
+
}
|
|
19
30
|
});
|
|
20
31
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { daemonErrorMessage } from './utils.js';
|
|
1
2
|
export function configRoutes(app, daemon) {
|
|
2
3
|
app.get('/api/config', async () => {
|
|
3
4
|
try {
|
|
@@ -7,7 +8,12 @@ export function configRoutes(app, daemon) {
|
|
|
7
8
|
return { ok: false, config: {} };
|
|
8
9
|
}
|
|
9
10
|
});
|
|
10
|
-
app.post('/api/config', async (req) => {
|
|
11
|
-
|
|
11
|
+
app.post('/api/config', async (req, reply) => {
|
|
12
|
+
try {
|
|
13
|
+
return await daemon.post('/api/config', req.body);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
reply.code(502).send({ ok: false, error: daemonErrorMessage(err) });
|
|
17
|
+
}
|
|
12
18
|
});
|
|
13
19
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HIGH_RISK_CAPABILITIES } from '@commandgarden/shared';
|
|
2
|
+
import { daemonErrorMessage } from './utils.js';
|
|
2
3
|
const APP_ROUTES = {
|
|
3
4
|
'timetracking/report': '/apps/timetracking',
|
|
4
5
|
'teams/room-availability': '/apps/rooms',
|
|
@@ -7,32 +8,42 @@ const APP_ROUTES = {
|
|
|
7
8
|
'tokenmaster/client-trustedby': '/apps/trusted-peer-expiry',
|
|
8
9
|
};
|
|
9
10
|
export function connectorRoutes(app, daemon) {
|
|
10
|
-
app.get('/api/connectors', async () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
11
|
+
app.get('/api/connectors', async (_req, reply) => {
|
|
12
|
+
try {
|
|
13
|
+
const [connectorData, configData] = await Promise.all([
|
|
14
|
+
daemon.get('/api/connectors'),
|
|
15
|
+
daemon.get('/api/config')
|
|
16
|
+
.catch(() => ({ ok: false, config: {} })),
|
|
17
|
+
]);
|
|
18
|
+
const security = (configData.config?.security ?? {});
|
|
19
|
+
const highRiskCaps = new Set(security.highRiskCapabilities ?? [...HIGH_RISK_CAPABILITIES]);
|
|
20
|
+
const approvedHighRisk = new Set(security.approvedHighRisk ?? []);
|
|
21
|
+
const autoApproveConnectors = new Set(security.autoApproveConnectors ?? []);
|
|
22
|
+
const enriched = connectorData.connectors.map((c) => {
|
|
23
|
+
const key = c.key;
|
|
24
|
+
const capabilities = c.capabilities ?? [];
|
|
25
|
+
return {
|
|
26
|
+
...c,
|
|
27
|
+
hasAppPage: key in APP_ROUTES,
|
|
28
|
+
appRoute: APP_ROUTES[key] ?? null,
|
|
29
|
+
isHighRisk: capabilities.some(cap => highRiskCaps.has(cap)),
|
|
30
|
+
isApproved: approvedHighRisk.has(key),
|
|
31
|
+
isAutoApproved: autoApproveConnectors.has(key),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
return { ok: true, connectors: enriched };
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
reply.code(502).send({ ok: false, error: daemonErrorMessage(err) });
|
|
38
|
+
}
|
|
33
39
|
});
|
|
34
|
-
app.get('/api/connectors/:site/:name', async (req) => {
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
app.get('/api/connectors/:site/:name', async (req, reply) => {
|
|
41
|
+
try {
|
|
42
|
+
const { site, name } = req.params;
|
|
43
|
+
return await daemon.get(`/api/connectors/${site}/${name}`);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
reply.code(502).send({ ok: false, error: daemonErrorMessage(err) });
|
|
47
|
+
}
|
|
37
48
|
});
|
|
38
49
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { daemonErrorMessage } from './utils.js';
|
|
1
2
|
export function runRoutes(app, daemon) {
|
|
2
3
|
app.post('/api/run', async (req, reply) => {
|
|
3
4
|
try {
|
|
4
5
|
return await daemon.post('/api/run', req.body);
|
|
5
6
|
}
|
|
6
7
|
catch (err) {
|
|
7
|
-
|
|
8
|
-
reply.code(502).send({ ok: false, error: message });
|
|
8
|
+
reply.code(502).send({ ok: false, error: daemonErrorMessage(err) });
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
app.post('/api/approval', async (req, reply) => {
|
|
@@ -13,8 +13,7 @@ export function runRoutes(app, daemon) {
|
|
|
13
13
|
return await daemon.post('/api/approval', req.body);
|
|
14
14
|
}
|
|
15
15
|
catch (err) {
|
|
16
|
-
|
|
17
|
-
reply.code(502).send({ ok: false, error: message });
|
|
16
|
+
reply.code(502).send({ ok: false, error: daemonErrorMessage(err) });
|
|
18
17
|
}
|
|
19
18
|
});
|
|
20
19
|
app.get('/api/run/events/:requestId', async (req, reply) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { daemonErrorMessage } from './utils.js';
|
|
1
2
|
export function statusRoutes(app, daemon) {
|
|
2
3
|
app.get('/api/status', async (req, reply) => {
|
|
3
4
|
try {
|
|
@@ -5,7 +6,7 @@ export function statusRoutes(app, daemon) {
|
|
|
5
6
|
return data;
|
|
6
7
|
}
|
|
7
8
|
catch (err) {
|
|
8
|
-
reply.code(503).send({ ok: false, error: err
|
|
9
|
+
reply.code(503).send({ ok: false, error: daemonErrorMessage(err) });
|
|
9
10
|
}
|
|
10
11
|
});
|
|
11
12
|
}
|
|
@@ -49,16 +49,16 @@ export class AppStore {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
migrate() {
|
|
52
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS preferences (
|
|
53
|
-
key TEXT PRIMARY KEY,
|
|
54
|
-
value TEXT NOT NULL
|
|
52
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS preferences (
|
|
53
|
+
key TEXT PRIMARY KEY,
|
|
54
|
+
value TEXT NOT NULL
|
|
55
55
|
)`);
|
|
56
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS saved_views (
|
|
57
|
-
id TEXT PRIMARY KEY,
|
|
58
|
-
app TEXT NOT NULL,
|
|
59
|
-
name TEXT NOT NULL,
|
|
60
|
-
config TEXT NOT NULL,
|
|
61
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
56
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS saved_views (
|
|
57
|
+
id TEXT PRIMARY KEY,
|
|
58
|
+
app TEXT NOT NULL,
|
|
59
|
+
name TEXT NOT NULL,
|
|
60
|
+
config TEXT NOT NULL,
|
|
61
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
62
62
|
)`);
|
|
63
63
|
// Drop legacy goals table (had category column) and recreate with activity
|
|
64
64
|
try {
|
|
@@ -76,41 +76,41 @@ export class AppStore {
|
|
|
76
76
|
}
|
|
77
77
|
catch { /* ignore */ }
|
|
78
78
|
}
|
|
79
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS goals (
|
|
80
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
81
|
-
month TEXT NOT NULL,
|
|
82
|
-
project_id TEXT NOT NULL,
|
|
83
|
-
activity TEXT NOT NULL DEFAULT '',
|
|
84
|
-
target_days REAL NOT NULL,
|
|
85
|
-
target_hours REAL NOT NULL,
|
|
86
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
87
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
88
|
-
UNIQUE(month, project_id, activity)
|
|
79
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS goals (
|
|
80
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
81
|
+
month TEXT NOT NULL,
|
|
82
|
+
project_id TEXT NOT NULL,
|
|
83
|
+
activity TEXT NOT NULL DEFAULT '',
|
|
84
|
+
target_days REAL NOT NULL,
|
|
85
|
+
target_hours REAL NOT NULL,
|
|
86
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
87
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
88
|
+
UNIQUE(month, project_id, activity)
|
|
89
89
|
)`);
|
|
90
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS report_cache (
|
|
91
|
-
month TEXT PRIMARY KEY,
|
|
92
|
-
data TEXT NOT NULL,
|
|
93
|
-
fetched_at TEXT NOT NULL
|
|
90
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS report_cache (
|
|
91
|
+
month TEXT PRIMARY KEY,
|
|
92
|
+
data TEXT NOT NULL,
|
|
93
|
+
fetched_at TEXT NOT NULL
|
|
94
94
|
)`);
|
|
95
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS projects_cache (
|
|
96
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
97
|
-
data TEXT NOT NULL,
|
|
98
|
-
fetched_at TEXT NOT NULL
|
|
95
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS projects_cache (
|
|
96
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
97
|
+
data TEXT NOT NULL,
|
|
98
|
+
fetched_at TEXT NOT NULL
|
|
99
99
|
)`);
|
|
100
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS security_news_cache (
|
|
101
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
102
|
-
data TEXT NOT NULL,
|
|
103
|
-
fetched_at TEXT NOT NULL
|
|
100
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS security_news_cache (
|
|
101
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
102
|
+
data TEXT NOT NULL,
|
|
103
|
+
fetched_at TEXT NOT NULL
|
|
104
104
|
)`);
|
|
105
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS trusted_peers_cache (
|
|
106
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
107
|
-
data TEXT NOT NULL,
|
|
108
|
-
fetched_at TEXT NOT NULL
|
|
105
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS trusted_peers_cache (
|
|
106
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
107
|
+
data TEXT NOT NULL,
|
|
108
|
+
fetched_at TEXT NOT NULL
|
|
109
109
|
)`);
|
|
110
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS room_availability_cache (
|
|
111
|
-
date TEXT PRIMARY KEY,
|
|
112
|
-
data TEXT NOT NULL,
|
|
113
|
-
fetched_at TEXT NOT NULL
|
|
110
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS room_availability_cache (
|
|
111
|
+
date TEXT PRIMARY KEY,
|
|
112
|
+
data TEXT NOT NULL,
|
|
113
|
+
fetched_at TEXT NOT NULL
|
|
114
114
|
)`);
|
|
115
115
|
// Legacy schema keyed by cache_key (weekStart + sources signature) —
|
|
116
116
|
// migrate to a plain weekStart-keyed table (one cached result per week,
|
|
@@ -121,10 +121,10 @@ export class AppStore {
|
|
|
121
121
|
this.db.run(`DROP TABLE journal_cache`);
|
|
122
122
|
}
|
|
123
123
|
catch { /* already on new schema, or table doesn't exist yet */ }
|
|
124
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS journal_cache (
|
|
125
|
-
week_start TEXT PRIMARY KEY,
|
|
126
|
-
data TEXT NOT NULL,
|
|
127
|
-
fetched_at TEXT NOT NULL
|
|
124
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS journal_cache (
|
|
125
|
+
week_start TEXT PRIMARY KEY,
|
|
126
|
+
data TEXT NOT NULL,
|
|
127
|
+
fetched_at TEXT NOT NULL
|
|
128
128
|
)`);
|
|
129
129
|
// saba_data holds the last Saba "pending training" connector result for
|
|
130
130
|
// the week, saved separately from `data` since it's fetched client-side
|
|
@@ -137,14 +137,14 @@ export class AppStore {
|
|
|
137
137
|
// Remembers the last-selected source toggles (Timetracking/Meetings/Jira/
|
|
138
138
|
// Git/Saba) so the UI can restore the user's picks on next load, instead
|
|
139
139
|
// of always resetting to "all enabled".
|
|
140
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS journal_source_prefs (
|
|
141
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
142
|
-
timetracking INTEGER NOT NULL DEFAULT 1,
|
|
143
|
-
meetings INTEGER NOT NULL DEFAULT 1,
|
|
144
|
-
jira INTEGER NOT NULL DEFAULT 1,
|
|
145
|
-
git INTEGER NOT NULL DEFAULT 1,
|
|
146
|
-
saba INTEGER NOT NULL DEFAULT 1,
|
|
147
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
140
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS journal_source_prefs (
|
|
141
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
142
|
+
timetracking INTEGER NOT NULL DEFAULT 1,
|
|
143
|
+
meetings INTEGER NOT NULL DEFAULT 1,
|
|
144
|
+
jira INTEGER NOT NULL DEFAULT 1,
|
|
145
|
+
git INTEGER NOT NULL DEFAULT 1,
|
|
146
|
+
saba INTEGER NOT NULL DEFAULT 1,
|
|
147
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
148
148
|
)`);
|
|
149
149
|
}
|
|
150
150
|
getPreference(key) {
|
|
@@ -291,7 +291,7 @@ export class AppStore {
|
|
|
291
291
|
// ON CONFLICT (rather than INSERT OR REPLACE) so an existing saba_data
|
|
292
292
|
// value for this week isn't wiped out when the main journal result is
|
|
293
293
|
// (re)generated — the two are cached independently.
|
|
294
|
-
this.db.run(`INSERT INTO journal_cache (week_start, data, fetched_at) VALUES (?, ?, ?)
|
|
294
|
+
this.db.run(`INSERT INTO journal_cache (week_start, data, fetched_at) VALUES (?, ?, ?)
|
|
295
295
|
ON CONFLICT(week_start) DO UPDATE SET data = excluded.data, fetched_at = excluded.fetched_at`, [weekStart, JSON.stringify(data), now]);
|
|
296
296
|
this.persist();
|
|
297
297
|
return now;
|
|
@@ -332,7 +332,7 @@ export class AppStore {
|
|
|
332
332
|
}
|
|
333
333
|
saveJournalSourcePrefs(sources) {
|
|
334
334
|
const now = new Date().toISOString();
|
|
335
|
-
this.db.run(`INSERT OR REPLACE INTO journal_source_prefs (id, timetracking, meetings, jira, git, saba, updated_at)
|
|
335
|
+
this.db.run(`INSERT OR REPLACE INTO journal_source_prefs (id, timetracking, meetings, jira, git, saba, updated_at)
|
|
336
336
|
VALUES (1, ?, ?, ?, ?, ?, ?)`, [
|
|
337
337
|
sources.timetracking ? 1 : 0,
|
|
338
338
|
sources.meetings ? 1 : 0,
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@commandgarden/app",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"files": [
|
|
7
|
-
"dist"
|
|
8
|
-
],
|
|
9
|
-
"scripts": {
|
|
10
|
-
"build": "vite build && tsc -p tsconfig.server.json",
|
|
11
|
-
"dev": "concurrently \"vite\" \"tsx watch src/server/main.ts\"",
|
|
12
|
-
"start": "node dist/server/main.js",
|
|
13
|
-
"test": "vitest run",
|
|
14
|
-
"test:watch": "vitest"
|
|
15
|
-
},
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"@commandgarden/shared": "*",
|
|
18
|
-
"@fastify/static": "^8.0.0",
|
|
19
|
-
"fastify": "^5.0.0",
|
|
20
|
-
"lucide-react": "^1.23.0",
|
|
21
|
-
"react": "^18.3.0",
|
|
22
|
-
"react-dom": "^18.3.0",
|
|
23
|
-
"react-router-dom": "^6.23.0",
|
|
24
|
-
"sql.js": "^1.11.0",
|
|
25
|
-
"yaml": "^2.4.0"
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@types/node": "^22.0.0",
|
|
29
|
-
"@types/react": "^18.3.0",
|
|
30
|
-
"@types/react-dom": "^18.3.0",
|
|
31
|
-
"@vitejs/plugin-react": "^4.3.0",
|
|
32
|
-
"autoprefixer": "^10.4.0",
|
|
33
|
-
"concurrently": "^9.0.0",
|
|
34
|
-
"daisyui": "^4.12.0",
|
|
35
|
-
"postcss": "^8.4.0",
|
|
36
|
-
"tailwindcss": "^3.4.0",
|
|
37
|
-
"tsx": "^4.0.0",
|
|
38
|
-
"typescript": "^5.4.0",
|
|
39
|
-
"vite": "^5.4.0",
|
|
40
|
-
"vitest": "^2.0.0"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@commandgarden/app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "vite build && tsc -p tsconfig.server.json",
|
|
11
|
+
"dev": "concurrently \"vite\" \"tsx watch src/server/main.ts\"",
|
|
12
|
+
"start": "node dist/server/main.js",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@commandgarden/shared": "*",
|
|
18
|
+
"@fastify/static": "^8.0.0",
|
|
19
|
+
"fastify": "^5.0.0",
|
|
20
|
+
"lucide-react": "^1.23.0",
|
|
21
|
+
"react": "^18.3.0",
|
|
22
|
+
"react-dom": "^18.3.0",
|
|
23
|
+
"react-router-dom": "^6.23.0",
|
|
24
|
+
"sql.js": "^1.11.0",
|
|
25
|
+
"yaml": "^2.4.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"@types/react": "^18.3.0",
|
|
30
|
+
"@types/react-dom": "^18.3.0",
|
|
31
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
32
|
+
"autoprefixer": "^10.4.0",
|
|
33
|
+
"concurrently": "^9.0.0",
|
|
34
|
+
"daisyui": "^4.12.0",
|
|
35
|
+
"postcss": "^8.4.0",
|
|
36
|
+
"tailwindcss": "^3.4.0",
|
|
37
|
+
"tsx": "^4.0.0",
|
|
38
|
+
"typescript": "^5.4.0",
|
|
39
|
+
"vite": "^5.4.0",
|
|
40
|
+
"vitest": "^2.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|