@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.
Files changed (66) hide show
  1. package/dist/main.js +58 -28
  2. package/node_modules/@commandgarden/app/dist/client/index.html +17 -17
  3. package/node_modules/@commandgarden/app/dist/server/main.js +1 -1
  4. package/node_modules/@commandgarden/app/dist/server/routes/audit.js +27 -16
  5. package/node_modules/@commandgarden/app/dist/server/routes/config.js +8 -2
  6. package/node_modules/@commandgarden/app/dist/server/routes/connectors.js +37 -26
  7. package/node_modules/@commandgarden/app/dist/server/routes/run.js +3 -4
  8. package/node_modules/@commandgarden/app/dist/server/routes/status.js +2 -1
  9. package/node_modules/@commandgarden/app/dist/server/routes/utils.d.ts +2 -0
  10. package/node_modules/@commandgarden/app/dist/server/routes/utils.js +4 -0
  11. package/node_modules/@commandgarden/app/dist/server/store.js +53 -53
  12. package/node_modules/@commandgarden/app/package.json +42 -42
  13. package/node_modules/@commandgarden/chrome/dist/content-script.js.map +1 -1
  14. package/node_modules/@commandgarden/chrome/dist/manifest.json +26 -26
  15. package/node_modules/@commandgarden/chrome/dist/popup.html +218 -218
  16. package/node_modules/@commandgarden/chrome/dist/popup.js.map +1 -1
  17. package/node_modules/@commandgarden/chrome/dist/service-worker.js.map +1 -1
  18. package/node_modules/@commandgarden/chrome/package.json +26 -26
  19. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.eval.js +100 -100
  20. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.yaml +71 -71
  21. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-content.yaml +61 -61
  22. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-pages.yaml +54 -54
  23. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.eval.js +115 -115
  24. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.yaml +54 -54
  25. package/node_modules/@commandgarden/daemon/connectors/lib/msal-token.js +20 -20
  26. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.eval.js +204 -204
  27. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.yaml +42 -42
  28. package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.eval.js +94 -94
  29. package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.yaml +34 -34
  30. package/node_modules/@commandgarden/daemon/connectors/socket-security-news.yaml +63 -63
  31. package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.eval.js +437 -437
  32. package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.yaml +53 -53
  33. package/node_modules/@commandgarden/daemon/connectors/teams-rooms-availability.eval.js +547 -547
  34. package/node_modules/@commandgarden/daemon/connectors/teams-rooms-availability.yaml +48 -48
  35. package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.eval.js +52 -52
  36. package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.yaml +42 -42
  37. package/node_modules/@commandgarden/daemon/connectors/timetracking-report.eval.js +81 -81
  38. package/node_modules/@commandgarden/daemon/connectors/timetracking-report.yaml +52 -52
  39. package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.eval.js +27 -27
  40. package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.yaml +38 -38
  41. package/node_modules/@commandgarden/daemon/connectors/tokenmaster-clients-list.yaml +55 -55
  42. package/node_modules/@commandgarden/daemon/dist/audit-store.js +8 -8
  43. package/node_modules/@commandgarden/daemon/dist/auth.d.ts +1 -0
  44. package/node_modules/@commandgarden/daemon/dist/auth.d.ts.map +1 -1
  45. package/node_modules/@commandgarden/daemon/dist/auth.js +10 -0
  46. package/node_modules/@commandgarden/daemon/dist/auth.js.map +1 -1
  47. package/node_modules/@commandgarden/daemon/dist/main.js +12 -5
  48. package/node_modules/@commandgarden/daemon/dist/main.js.map +1 -1
  49. package/node_modules/@commandgarden/daemon/dist/server.d.ts.map +1 -1
  50. package/node_modules/@commandgarden/daemon/dist/server.js +10 -0
  51. package/node_modules/@commandgarden/daemon/dist/server.js.map +1 -1
  52. package/node_modules/@commandgarden/daemon/package.json +32 -32
  53. package/node_modules/@commandgarden/shared/dist/daemon-client.d.ts +5 -1
  54. package/node_modules/@commandgarden/shared/dist/daemon-client.d.ts.map +1 -1
  55. package/node_modules/@commandgarden/shared/dist/daemon-client.js +32 -14
  56. package/node_modules/@commandgarden/shared/dist/daemon-client.js.map +1 -1
  57. package/node_modules/@commandgarden/shared/package.json +32 -32
  58. package/package.json +58 -58
  59. package/node_modules/@commandgarden/app/dist/server/daemon-client.test.js +0 -47
  60. package/node_modules/@commandgarden/app/dist/server/routes/routes.test.js +0 -137
  61. package/node_modules/@commandgarden/app/dist/server/store.test.js +0 -59
  62. package/node_modules/@commandgarden/chrome/dist/fetch-interceptor.js +0 -44
  63. package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts +0 -24
  64. package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts.map +0 -1
  65. package/node_modules/@commandgarden/daemon/dist/cdp-client.js +0 -150
  66. 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
- constructor(baseUrl, token) {
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.rawFetch(path, {
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.rawFetch(path, {
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.rawFetch(path, {
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, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2, mkdirSync, openSync, closeSync } from "fs";
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 (!existsSync3(pidPath)) {
17136
- return "Daemon is not running (no PID file found).";
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
- process.kill(pid, "SIGTERM");
17141
- unlinkSync2(pidPath);
17142
- return `Daemon stopped (PID: ${pid}).`;
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 writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync3, mkdirSync as mkdirSync2, openSync as openSync2, closeSync as closeSync2 } from "fs";
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) writeFileSync3(pidPath, String(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
- writeFileSync3(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${line}
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
- const query = req.query;
4
- const params = new URLSearchParams();
5
- if (query.since)
6
- params.set('since', query.since);
7
- if (query.connector)
8
- params.set('connector', query.connector);
9
- if (query.type)
10
- params.set('type', query.type);
11
- if (query.limit)
12
- params.set('limit', query.limit);
13
- const qs = params.toString();
14
- return daemon.get(`/api/audit${qs ? `?${qs}` : ''}`);
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
- const { id } = req.params;
18
- return daemon.get(`/api/audit/${id}`);
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
- return daemon.post('/api/config', req.body);
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
- const [connectorData, configData] = await Promise.all([
12
- daemon.get('/api/connectors'),
13
- daemon.get('/api/config')
14
- .catch(() => ({ ok: false, config: {} })),
15
- ]);
16
- const security = (configData.config?.security ?? {});
17
- const highRiskCaps = new Set(security.highRiskCapabilities ?? [...HIGH_RISK_CAPABILITIES]);
18
- const approvedHighRisk = new Set(security.approvedHighRisk ?? []);
19
- const autoApproveConnectors = new Set(security.autoApproveConnectors ?? []);
20
- const enriched = connectorData.connectors.map((c) => {
21
- const key = c.key;
22
- const capabilities = c.capabilities ?? [];
23
- return {
24
- ...c,
25
- hasAppPage: key in APP_ROUTES,
26
- appRoute: APP_ROUTES[key] ?? null,
27
- isHighRisk: capabilities.some(cap => highRiskCaps.has(cap)),
28
- isApproved: approvedHighRisk.has(key),
29
- isAutoApproved: autoApproveConnectors.has(key),
30
- };
31
- });
32
- return { ok: true, connectors: enriched };
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
- const { site, name } = req.params;
36
- return daemon.get(`/api/connectors/${site}/${name}`);
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
- const message = err instanceof Error ? err.message : 'Unknown error';
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
- const message = err instanceof Error ? err.message : 'Unknown error';
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 instanceof Error ? err.message : 'Daemon unreachable' });
9
+ reply.code(503).send({ ok: false, error: daemonErrorMessage(err) });
9
10
  }
10
11
  });
11
12
  }
@@ -0,0 +1,2 @@
1
+ /** Extract a human-readable message from an unknown catch value. */
2
+ export declare function daemonErrorMessage(err: unknown): string;
@@ -0,0 +1,4 @@
1
+ /** Extract a human-readable message from an unknown catch value. */
2
+ export function daemonErrorMessage(err) {
3
+ return err instanceof Error ? err.message : 'Daemon unreachable';
4
+ }
@@ -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
+ }