@commandgarden/cli 1.4.1 → 2.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.
Files changed (81) hide show
  1. package/dist/main.js +6 -12
  2. package/node_modules/@commandgarden/app/dist/client/assets/index-CThI0UCl.css +1 -0
  3. package/node_modules/@commandgarden/app/dist/client/assets/index-WANip_um.js +252 -0
  4. package/node_modules/@commandgarden/app/dist/client/index.html +2 -2
  5. package/node_modules/@commandgarden/app/dist/server/journal/journal.config.d.ts +25 -0
  6. package/node_modules/@commandgarden/app/dist/server/journal/journal.config.js +31 -0
  7. package/node_modules/@commandgarden/app/dist/server/journal/journal.service.d.ts +37 -0
  8. package/node_modules/@commandgarden/app/dist/server/journal/journal.service.js +142 -0
  9. package/node_modules/@commandgarden/app/dist/server/journal/journal.types.d.ts +91 -0
  10. package/node_modules/@commandgarden/app/dist/server/journal/journal.types.js +4 -0
  11. package/node_modules/@commandgarden/app/dist/server/journal/sources/git.source.d.ts +21 -0
  12. package/node_modules/@commandgarden/app/dist/server/journal/sources/git.source.js +72 -0
  13. package/node_modules/@commandgarden/app/dist/server/journal/sources/jira.source.d.ts +15 -0
  14. package/node_modules/@commandgarden/app/dist/server/journal/sources/jira.source.js +42 -0
  15. package/node_modules/@commandgarden/app/dist/server/journal/sources/meetings.source.d.ts +21 -0
  16. package/node_modules/@commandgarden/app/dist/server/journal/sources/meetings.source.js +25 -0
  17. package/node_modules/@commandgarden/app/dist/server/journal/sources/timetracking.source.d.ts +17 -0
  18. package/node_modules/@commandgarden/app/dist/server/journal/sources/timetracking.source.js +83 -0
  19. package/node_modules/@commandgarden/app/dist/server/routes/index.js +2 -0
  20. package/node_modules/@commandgarden/app/dist/server/routes/journal.d.ts +9 -0
  21. package/node_modules/@commandgarden/app/dist/server/routes/journal.js +56 -0
  22. package/node_modules/@commandgarden/chrome/dist/content-script.js +84 -2
  23. package/node_modules/@commandgarden/chrome/dist/content-script.js.map +2 -2
  24. package/node_modules/@commandgarden/chrome/dist/service-worker.js +73 -8
  25. package/node_modules/@commandgarden/chrome/dist/service-worker.js.map +3 -3
  26. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.eval.js +143 -0
  27. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.yaml +70 -0
  28. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-content.yaml +58 -0
  29. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-pages.yaml +47 -0
  30. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.eval.js +115 -0
  31. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.yaml +54 -0
  32. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.eval.js +186 -0
  33. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.yaml +41 -0
  34. package/node_modules/@commandgarden/daemon/connectors/socket-security-news.yaml +63 -0
  35. package/node_modules/@commandgarden/daemon/connectors/tokenmaster-client-trustedby.yaml +62 -0
  36. package/node_modules/@commandgarden/daemon/connectors/tokenmaster-clients-list.yaml +55 -0
  37. package/node_modules/@commandgarden/daemon/connectors/wiz-blog-security.eval.js +29 -0
  38. package/node_modules/@commandgarden/daemon/connectors/wiz-blog-security.yaml +44 -0
  39. package/node_modules/@commandgarden/daemon/dist/daemon-runner.d.ts +12 -0
  40. package/node_modules/@commandgarden/daemon/dist/daemon-runner.d.ts.map +1 -0
  41. package/node_modules/@commandgarden/daemon/dist/daemon-runner.js +65 -0
  42. package/node_modules/@commandgarden/daemon/dist/daemon-runner.js.map +1 -0
  43. package/node_modules/@commandgarden/daemon/dist/server.d.ts.map +1 -1
  44. package/node_modules/@commandgarden/daemon/dist/server.js +87 -13
  45. package/node_modules/@commandgarden/daemon/dist/server.js.map +1 -1
  46. package/node_modules/@commandgarden/daemon/dist/transforms.d.ts +3 -0
  47. package/node_modules/@commandgarden/daemon/dist/transforms.d.ts.map +1 -0
  48. package/node_modules/@commandgarden/daemon/dist/transforms.js +36 -0
  49. package/node_modules/@commandgarden/daemon/dist/transforms.js.map +1 -0
  50. package/node_modules/@commandgarden/daemon/package.json +4 -2
  51. package/node_modules/@commandgarden/shared/dist/connector.d.ts +175 -0
  52. package/node_modules/@commandgarden/shared/dist/connector.d.ts.map +1 -1
  53. package/node_modules/@commandgarden/shared/dist/connector.js +2 -0
  54. package/node_modules/@commandgarden/shared/dist/connector.js.map +1 -1
  55. package/node_modules/@commandgarden/shared/dist/expression/evaluator.d.ts +2 -0
  56. package/node_modules/@commandgarden/shared/dist/expression/evaluator.d.ts.map +1 -1
  57. package/node_modules/@commandgarden/shared/dist/expression/evaluator.js.map +1 -1
  58. package/node_modules/@commandgarden/shared/dist/expression/filters.d.ts.map +1 -1
  59. package/node_modules/@commandgarden/shared/dist/expression/filters.js +5 -0
  60. package/node_modules/@commandgarden/shared/dist/expression/filters.js.map +1 -1
  61. package/node_modules/@commandgarden/shared/dist/fan-out.d.ts +28 -0
  62. package/node_modules/@commandgarden/shared/dist/fan-out.d.ts.map +1 -0
  63. package/node_modules/@commandgarden/shared/dist/fan-out.js +55 -0
  64. package/node_modules/@commandgarden/shared/dist/fan-out.js.map +1 -0
  65. package/node_modules/@commandgarden/shared/dist/index.d.ts +2 -1
  66. package/node_modules/@commandgarden/shared/dist/index.d.ts.map +1 -1
  67. package/node_modules/@commandgarden/shared/dist/index.js +3 -1
  68. package/node_modules/@commandgarden/shared/dist/index.js.map +1 -1
  69. package/node_modules/@commandgarden/shared/dist/loader.d.ts.map +1 -1
  70. package/node_modules/@commandgarden/shared/dist/loader.js +17 -0
  71. package/node_modules/@commandgarden/shared/dist/loader.js.map +1 -1
  72. package/node_modules/@commandgarden/shared/dist/pipeline.d.ts +223 -1
  73. package/node_modules/@commandgarden/shared/dist/pipeline.d.ts.map +1 -1
  74. package/node_modules/@commandgarden/shared/dist/pipeline.js +58 -2
  75. package/node_modules/@commandgarden/shared/dist/pipeline.js.map +1 -1
  76. package/node_modules/@commandgarden/shared/dist/protocol.d.ts +1 -0
  77. package/node_modules/@commandgarden/shared/dist/protocol.d.ts.map +1 -1
  78. package/node_modules/@commandgarden/shared/dist/protocol.js.map +1 -1
  79. package/package.json +1 -1
  80. package/node_modules/@commandgarden/app/dist/client/assets/index-B-_w54e9.css +0 -1
  81. package/node_modules/@commandgarden/app/dist/client/assets/index-Bb1BOK1E.js +0 -252
@@ -10,8 +10,8 @@
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
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
12
  <title>commandGarden</title>
13
- <script type="module" crossorigin src="/assets/index-Bb1BOK1E.js"></script>
14
- <link rel="stylesheet" crossorigin href="/assets/index-B-_w54e9.css">
13
+ <script type="module" crossorigin src="/assets/index-WANip_um.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-CThI0UCl.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Journal configuration — reads from commandGarden preferences (SQLite).
3
+ * Users configure their settings through the GUI Config page.
4
+ *
5
+ * Preference keys:
6
+ * journal.author — email/name as it appears in git commits and ADO
7
+ * journal.ado_org — Azure DevOps organization (e.g. "daimler-mic")
8
+ * journal.ado_repos — comma-separated "project/repo" pairs
9
+ * journal.target_hours — weekly target hours (default 40)
10
+ */
11
+ import type { AppStore } from '../store.js';
12
+ /** Build journal config from the user's stored preferences. */
13
+ export declare function getJournalConfig(store: AppStore): {
14
+ author: string;
15
+ azureDevOps: {
16
+ org: string;
17
+ repos: {
18
+ project: string;
19
+ repo: string;
20
+ }[];
21
+ };
22
+ targetHours: number;
23
+ };
24
+ /** Type of the config object returned by getJournalConfig. */
25
+ export type JournalConfig = ReturnType<typeof getJournalConfig>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Journal configuration — reads from commandGarden preferences (SQLite).
3
+ * Users configure their settings through the GUI Config page.
4
+ *
5
+ * Preference keys:
6
+ * journal.author — email/name as it appears in git commits and ADO
7
+ * journal.ado_org — Azure DevOps organization (e.g. "daimler-mic")
8
+ * journal.ado_repos — comma-separated "project/repo" pairs
9
+ * journal.target_hours — weekly target hours (default 40)
10
+ */
11
+ /** Parse comma-separated "project/repo" pairs.
12
+ * Example: "mic-dns/mic-dns-api,mic-dns/mic-dns-ui" */
13
+ function parseRepos(raw) {
14
+ if (!raw)
15
+ return [];
16
+ return raw.split(',').map((entry) => {
17
+ const [project, repo] = entry.trim().split('/');
18
+ return { project, repo };
19
+ });
20
+ }
21
+ /** Build journal config from the user's stored preferences. */
22
+ export function getJournalConfig(store) {
23
+ return {
24
+ author: store.getPreference('journal.author') ?? '',
25
+ azureDevOps: {
26
+ org: store.getPreference('journal.ado_org') ?? '',
27
+ repos: parseRepos(store.getPreference('journal.ado_repos') ?? ''),
28
+ },
29
+ targetHours: Number(store.getPreference('journal.target_hours')) || 40,
30
+ };
31
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Journal orchestrator — fetches all sources in parallel, cross-references,
3
+ * and generates basic insights.
4
+ * Ported from dashboard/api/src/journal/journal.service.ts.
5
+ *
6
+ * Removed NestJS DI: takes source instances via constructor params.
7
+ * Removed AuditService dependency: audit logging deferred to route level / Phase 3.
8
+ */
9
+ import { GitSource } from './sources/git.source.js';
10
+ import { TimetrackingSource } from './sources/timetracking.source.js';
11
+ import { MeetingsSource } from './sources/meetings.source.js';
12
+ import { JiraSource } from './sources/jira.source.js';
13
+ import type { JournalInput, JournalResponse } from './journal.types.js';
14
+ export declare class JournalService {
15
+ private readonly git;
16
+ private readonly timetracking;
17
+ private readonly meetings;
18
+ private readonly jira;
19
+ constructor(git: GitSource, timetracking: TimetrackingSource, meetings: MeetingsSource, jira: JiraSource);
20
+ generate(input: JournalInput): Promise<JournalResponse>;
21
+ /**
22
+ * Cross-reference data across sources to identify patterns:
23
+ * - forgottenDays: days with activity but 0 hours logged
24
+ * - heavyMeetingDays: days with 4+ hours in meetings
25
+ * - zeroCodingDays: weekdays with 0 commits
26
+ * - meetingRatio: fraction of work hours spent in meetings
27
+ * - codingHours: estimated non-meeting hours
28
+ */
29
+ private crossReference;
30
+ /**
31
+ * Generate basic insights without LLM.
32
+ * Phase 4 replaces this with an LLM call for richer, more contextual insights.
33
+ */
34
+ private generateBasicInsights;
35
+ private addDays;
36
+ private weekdaysInRange;
37
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Journal orchestrator — fetches all sources in parallel, cross-references,
3
+ * and generates basic insights.
4
+ * Ported from dashboard/api/src/journal/journal.service.ts.
5
+ *
6
+ * Removed NestJS DI: takes source instances via constructor params.
7
+ * Removed AuditService dependency: audit logging deferred to route level / Phase 3.
8
+ */
9
+ export class JournalService {
10
+ git;
11
+ timetracking;
12
+ meetings;
13
+ jira;
14
+ constructor(git, timetracking, meetings, jira) {
15
+ this.git = git;
16
+ this.timetracking = timetracking;
17
+ this.meetings = meetings;
18
+ this.jira = jira;
19
+ }
20
+ async generate(input) {
21
+ const weekStart = input.weekStart;
22
+ const weekEnd = this.addDays(weekStart, 6); // Mon–Sun (full 7-day week)
23
+ const errors = [];
24
+ // Fetch all sources in parallel; each source handles its own errors gracefully
25
+ const [ttResult, meetingsResult, jiraResult, gitResult] = await Promise.allSettled([
26
+ this.timetracking.fetch(weekStart, weekEnd),
27
+ this.meetings.fetch(weekStart, weekEnd),
28
+ this.jira.fetch(weekStart, weekEnd),
29
+ this.git.fetch(weekStart, weekEnd),
30
+ ]);
31
+ const tt = ttResult.status === 'fulfilled' ? ttResult.value : null;
32
+ if (ttResult.status === 'rejected')
33
+ errors.push(`TimeTracking: ${ttResult.reason}`);
34
+ const mtg = meetingsResult.status === 'fulfilled' ? meetingsResult.value : null;
35
+ if (meetingsResult.status === 'rejected')
36
+ errors.push(`Meetings: ${meetingsResult.reason}`);
37
+ const jira = jiraResult.status === 'fulfilled' ? jiraResult.value : null;
38
+ if (jiraResult.status === 'rejected')
39
+ errors.push(`Jira: ${jiraResult.reason}`);
40
+ const git = gitResult.status === 'fulfilled' ? gitResult.value : null;
41
+ if (gitResult.status === 'rejected')
42
+ errors.push(`Git: ${gitResult.reason}`);
43
+ // Cross-reference available data
44
+ const crossRef = this.crossReference(tt, mtg, jira, git, weekStart, weekEnd);
45
+ // Determine overall status
46
+ const hasAnyData = tt || mtg || jira || git;
47
+ const status = hasAnyData ? (errors.length > 0 ? 'partial' : 'success') : 'error';
48
+ // Rule-based insights (LLM replacement in Phase 4)
49
+ const insights = this.generateBasicInsights(tt, mtg, jira, git, crossRef);
50
+ return {
51
+ status,
52
+ week: `${weekStart} – ${weekEnd}`,
53
+ timetracking: tt,
54
+ meetings: mtg,
55
+ jira,
56
+ git,
57
+ crossRef,
58
+ insights,
59
+ errors,
60
+ };
61
+ }
62
+ /**
63
+ * Cross-reference data across sources to identify patterns:
64
+ * - forgottenDays: days with activity but 0 hours logged
65
+ * - heavyMeetingDays: days with 4+ hours in meetings
66
+ * - zeroCodingDays: weekdays with 0 commits
67
+ * - meetingRatio: fraction of work hours spent in meetings
68
+ * - codingHours: estimated non-meeting hours
69
+ */
70
+ crossReference(tt, mtg, _jira, git, weekStart, weekEnd) {
71
+ if (!tt && !mtg && !git)
72
+ return null;
73
+ // Days with activity but 0 hours logged in TimeTracking
74
+ const forgottenDays = [];
75
+ if (tt) {
76
+ for (const gap of tt.gaps) {
77
+ const hasMeetings = mtg?.daily.some((d) => d.date === gap && d.count > 0);
78
+ const hasCommits = git?.daily.some((d) => d.date === gap && d.commits > 0);
79
+ if (hasMeetings || hasCommits)
80
+ forgottenDays.push(gap);
81
+ }
82
+ }
83
+ // Heavy meeting days (4+ hours)
84
+ const heavyMeetingDays = (mtg?.daily ?? [])
85
+ .filter((d) => d.hours >= 4)
86
+ .map((d) => ({ date: d.date, hours: d.hours }));
87
+ // Days with 0 commits
88
+ const allWeekdays = this.weekdaysInRange(weekStart, weekEnd);
89
+ const zeroCodingDays = git
90
+ ? allWeekdays.filter((date) => !git.daily.some((d) => d.date === date && d.commits > 0))
91
+ : [];
92
+ // Meeting ratio
93
+ const totalWorkHours = tt?.totalHours ?? 40;
94
+ const meetingHours = mtg?.totalHours ?? 0;
95
+ const meetingRatio = totalWorkHours > 0 ? Math.round((meetingHours / totalWorkHours) * 100) / 100 : 0;
96
+ const codingHours = Math.max(0, totalWorkHours - meetingHours);
97
+ return { forgottenDays, heavyMeetingDays, zeroCodingDays, meetingRatio, codingHours };
98
+ }
99
+ /**
100
+ * Generate basic insights without LLM.
101
+ * Phase 4 replaces this with an LLM call for richer, more contextual insights.
102
+ */
103
+ generateBasicInsights(tt, mtg, jira, git, crossRef) {
104
+ const insights = [];
105
+ if (crossRef?.forgottenDays.length) {
106
+ insights.push(`You have activity on ${crossRef.forgottenDays.join(', ')} but 0 hours logged in TimeTracking — you may have forgotten to fill it.`);
107
+ }
108
+ if (crossRef?.heavyMeetingDays.length) {
109
+ const days = crossRef.heavyMeetingDays.map((d) => `${d.date} (${d.hours}h)`).join(', ');
110
+ insights.push(`Heavy meeting days: ${days}. Consider blocking focus time.`);
111
+ }
112
+ if (tt && tt.totalHours < tt.targetHours * 0.8) {
113
+ const gap = tt.targetHours - tt.totalHours;
114
+ insights.push(`You're ${gap}h below your ${tt.targetHours}h weekly target.`);
115
+ }
116
+ if (jira?.blockers.length) {
117
+ const items = jira.blockers.map((b) => `${b.key} (${b.staleDays}d)`).join(', ');
118
+ insights.push(`Stale tickets: ${items}. Consider unblocking or reassigning.`);
119
+ }
120
+ if (crossRef && crossRef.meetingRatio > 0.5 && mtg) {
121
+ insights.push(`Meetings consumed ${Math.round(crossRef.meetingRatio * 100)}% of your logged hours this week.`);
122
+ }
123
+ return insights;
124
+ }
125
+ addDays(dateStr, days) {
126
+ const d = new Date(dateStr);
127
+ d.setDate(d.getDate() + days);
128
+ return d.toISOString().slice(0, 10);
129
+ }
130
+ weekdaysInRange(start, end) {
131
+ const days = [];
132
+ const cursor = new Date(start);
133
+ const endDate = new Date(end);
134
+ while (cursor <= endDate) {
135
+ if (cursor.getDay() >= 1 && cursor.getDay() <= 5) {
136
+ days.push(cursor.toISOString().slice(0, 10));
137
+ }
138
+ cursor.setDate(cursor.getDate() + 1);
139
+ }
140
+ return days;
141
+ }
142
+ }
@@ -0,0 +1,91 @@
1
+ /** Shared types for the Dev Work Journal module.
2
+ * Ported from dashboard/api/src/journal/journal.types.ts — identical interfaces,
3
+ * no NestJS dependencies. */
4
+ export interface JournalInput {
5
+ weekStart: string;
6
+ }
7
+ export interface TtDailyEntry {
8
+ date: string;
9
+ hours: number;
10
+ projects: {
11
+ projectId: string;
12
+ hours: number;
13
+ }[];
14
+ }
15
+ export interface TimetrackingData {
16
+ totalHours: number;
17
+ targetHours: number;
18
+ daily: TtDailyEntry[];
19
+ gaps: string[];
20
+ }
21
+ export interface MeetingEntry {
22
+ date: string;
23
+ subject: string;
24
+ start: string;
25
+ end: string;
26
+ durationMin: number;
27
+ }
28
+ export interface MeetingsDaily {
29
+ date: string;
30
+ count: number;
31
+ hours: number;
32
+ }
33
+ export interface MeetingsData {
34
+ totalCount: number;
35
+ totalHours: number;
36
+ avgDurationMin: number;
37
+ daily: MeetingsDaily[];
38
+ entries: MeetingEntry[];
39
+ }
40
+ export interface JiraTicket {
41
+ key: string;
42
+ summary: string;
43
+ }
44
+ export interface JiraData {
45
+ resolved: (JiraTicket & {
46
+ resolvedDate: string;
47
+ })[];
48
+ inProgress: JiraTicket[];
49
+ blockers: (JiraTicket & {
50
+ staleDays: number;
51
+ })[];
52
+ }
53
+ export interface GitDaily {
54
+ date: string;
55
+ commits: number;
56
+ filesChanged: number;
57
+ }
58
+ export interface GitRepoActivity {
59
+ project: string;
60
+ repo: string;
61
+ commits: number;
62
+ }
63
+ export interface GitData {
64
+ totalCommits: number;
65
+ totalPRsMerged: number;
66
+ totalPRsReviewed: number;
67
+ daily: GitDaily[];
68
+ repos: GitRepoActivity[];
69
+ }
70
+ export interface CrossRefData {
71
+ forgottenDays: string[];
72
+ heavyMeetingDays: {
73
+ date: string;
74
+ hours: number;
75
+ }[];
76
+ zeroCodingDays: string[];
77
+ meetingRatio: number;
78
+ codingHours: number;
79
+ }
80
+ export type JournalStatus = 'success' | 'partial' | 'error';
81
+ export interface JournalResponse {
82
+ status: JournalStatus;
83
+ week: string;
84
+ timetracking: TimetrackingData | null;
85
+ meetings: MeetingsData | null;
86
+ jira: JiraData | null;
87
+ git: GitData | null;
88
+ crossRef: CrossRefData | null;
89
+ insights: string[];
90
+ errors: string[];
91
+ }
@@ -0,0 +1,4 @@
1
+ /** Shared types for the Dev Work Journal module.
2
+ * Ported from dashboard/api/src/journal/journal.types.ts — identical interfaces,
3
+ * no NestJS dependencies. */
4
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Git data source — fetches commits and PRs via the ado/git-commits
3
+ * commandGarden connector through the daemon API.
4
+ *
5
+ * Phase 3 rewrite: replaced direct REST API calls + PAT auth with a single
6
+ * daemon.post('/api/run') call per repo. The connector uses the browser's
7
+ * MSAL session token — no PAT required.
8
+ */
9
+ import type { DaemonClient } from '@commandgarden/shared';
10
+ import type { JournalConfig } from '../journal.config.js';
11
+ import type { GitData } from '../journal.types.js';
12
+ export declare class GitSource {
13
+ private readonly daemon;
14
+ private readonly org;
15
+ private readonly repos;
16
+ private readonly author;
17
+ constructor(daemon: DaemonClient, config: JournalConfig);
18
+ fetch(weekStart: string, weekEnd: string): Promise<GitData>;
19
+ /** Fetch a single repo's commits+PRs via the daemon connector. */
20
+ private fetchRepo;
21
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Git data source — fetches commits and PRs via the ado/git-commits
3
+ * commandGarden connector through the daemon API.
4
+ *
5
+ * Phase 3 rewrite: replaced direct REST API calls + PAT auth with a single
6
+ * daemon.post('/api/run') call per repo. The connector uses the browser's
7
+ * MSAL session token — no PAT required.
8
+ */
9
+ export class GitSource {
10
+ daemon;
11
+ org;
12
+ repos;
13
+ author;
14
+ constructor(daemon, config) {
15
+ this.daemon = daemon;
16
+ this.org = config.azureDevOps.org;
17
+ this.repos = config.azureDevOps.repos;
18
+ this.author = config.author;
19
+ }
20
+ async fetch(weekStart, weekEnd) {
21
+ try {
22
+ // Call the ado/git-commits connector once per configured repo via daemon
23
+ const results = await Promise.all(this.repos.map((r) => this.fetchRepo(r.project, r.repo, weekStart, weekEnd)));
24
+ // Aggregate daily commits across all repos
25
+ const dailyMap = new Map();
26
+ const repoActivity = [];
27
+ let totalPRsMerged = 0;
28
+ for (let i = 0; i < this.repos.length; i++) {
29
+ const { project, repo } = this.repos[i];
30
+ const rows = results[i];
31
+ const commitRows = rows.filter((r) => r.isPR !== 'true');
32
+ const prRows = rows.filter((r) => r.isPR === 'true');
33
+ repoActivity.push({ project, repo, commits: commitRows.length });
34
+ totalPRsMerged += prRows.length;
35
+ for (const r of commitRows) {
36
+ const date = r.date;
37
+ const entry = dailyMap.get(date) ?? { date, commits: 0, filesChanged: 0 };
38
+ entry.commits += 1;
39
+ entry.filesChanged += (r.filesAdded ?? 0) + (r.filesEdited ?? 0) + (r.filesDeleted ?? 0);
40
+ dailyMap.set(date, entry);
41
+ }
42
+ }
43
+ const daily = [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date));
44
+ const totalCommits = daily.reduce((sum, d) => sum + d.commits, 0);
45
+ return {
46
+ totalCommits,
47
+ totalPRsMerged,
48
+ totalPRsReviewed: 0,
49
+ daily,
50
+ repos: repoActivity.filter((r) => r.commits > 0),
51
+ };
52
+ }
53
+ catch (e) {
54
+ console.error('[git] fetch error:', e);
55
+ // Graceful degradation: return empty data instead of failing
56
+ return { totalCommits: 0, totalPRsMerged: 0, totalPRsReviewed: 0, daily: [], repos: [] };
57
+ }
58
+ }
59
+ /** Fetch a single repo's commits+PRs via the daemon connector. */
60
+ async fetchRepo(project, repo, fromDate, toDate) {
61
+ try {
62
+ const result = await this.daemon.post('/api/run', {
63
+ connector: 'ado/git-commits',
64
+ args: { org: this.org, project, repo, fromDate, toDate, author: this.author },
65
+ });
66
+ return result.ok && result.data ? result.data : [];
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Jira Cloud data source — fetches tickets via the jira/my-tickets
3
+ * commandGarden connector through the daemon API.
4
+ *
5
+ * Uses Jira Cloud REST API (mercedes-benz.atlassian.net) with the browser's
6
+ * Atlassian session — no API token required. Connector returns rows with a
7
+ * `category` field (resolved/inProgress/blocker) and Jira issue keys (e.g. PROJ-123).
8
+ */
9
+ import type { DaemonClient } from '@commandgarden/shared';
10
+ import type { JiraData } from '../journal.types.js';
11
+ export declare class JiraSource {
12
+ private readonly daemon;
13
+ constructor(daemon: DaemonClient);
14
+ fetch(weekStart: string, weekEnd: string): Promise<JiraData | null>;
15
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Jira Cloud data source — fetches tickets via the jira/my-tickets
3
+ * commandGarden connector through the daemon API.
4
+ *
5
+ * Uses Jira Cloud REST API (mercedes-benz.atlassian.net) with the browser's
6
+ * Atlassian session — no API token required. Connector returns rows with a
7
+ * `category` field (resolved/inProgress/blocker) and Jira issue keys (e.g. PROJ-123).
8
+ */
9
+ export class JiraSource {
10
+ daemon;
11
+ constructor(daemon) {
12
+ this.daemon = daemon;
13
+ }
14
+ async fetch(weekStart, weekEnd) {
15
+ try {
16
+ // assignee left empty → connector uses Jira's currentUser() function
17
+ const result = await this.daemon.post('/api/run', {
18
+ connector: 'jira/my-tickets',
19
+ args: { fromDate: weekStart, toDate: weekEnd },
20
+ });
21
+ if (!result.ok || !result.data || result.data.length === 0)
22
+ return null;
23
+ // Map connector rows by category to the JiraData sub-arrays.
24
+ // Connector returns Jira issue keys directly (e.g. "PROJ-123").
25
+ const rows = result.data;
26
+ return {
27
+ resolved: rows
28
+ .filter((r) => r.category === 'resolved')
29
+ .map((r) => ({ key: r.key, summary: r.summary, resolvedDate: r.resolvedDate })),
30
+ inProgress: rows
31
+ .filter((r) => r.category === 'inProgress')
32
+ .map((r) => ({ key: r.key, summary: r.summary })),
33
+ blockers: rows
34
+ .filter((r) => r.category === 'blocker')
35
+ .map((r) => ({ key: r.key, summary: r.summary, staleDays: r.staleDays })),
36
+ };
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Meetings data source — DISABLED for v1.
3
+ *
4
+ * The Outlook connector is blocked by the corporate MCAS proxy
5
+ * (outlook.cloud.microsoft.mcas.ms) which prevents all standard auth patterns:
6
+ * - OWA REST API: X-OWA-CANARY cookie is HttpOnly (JS can't read it)
7
+ * - Graph API: no Graph-scoped MSAL token available in MCAS session storage
8
+ * - Response interception: eval script installs after OWA's initial data fetch
9
+ *
10
+ * Returns null immediately — the journal renders without meeting data
11
+ * (graceful degradation). Git + Jira provide a strong 2-source story.
12
+ *
13
+ * TODO: Revisit post-demo with MCAS-specific auth investigation.
14
+ */
15
+ import type { DaemonClient } from '@commandgarden/shared';
16
+ import type { MeetingsData } from '../journal.types.js';
17
+ export declare class MeetingsSource {
18
+ private readonly _daemon;
19
+ constructor(_daemon: DaemonClient);
20
+ fetch(_weekStart: string, _weekEnd: string): Promise<MeetingsData | null>;
21
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Meetings data source — DISABLED for v1.
3
+ *
4
+ * The Outlook connector is blocked by the corporate MCAS proxy
5
+ * (outlook.cloud.microsoft.mcas.ms) which prevents all standard auth patterns:
6
+ * - OWA REST API: X-OWA-CANARY cookie is HttpOnly (JS can't read it)
7
+ * - Graph API: no Graph-scoped MSAL token available in MCAS session storage
8
+ * - Response interception: eval script installs after OWA's initial data fetch
9
+ *
10
+ * Returns null immediately — the journal renders without meeting data
11
+ * (graceful degradation). Git + Jira provide a strong 2-source story.
12
+ *
13
+ * TODO: Revisit post-demo with MCAS-specific auth investigation.
14
+ */
15
+ export class MeetingsSource {
16
+ _daemon;
17
+ constructor(_daemon) {
18
+ this._daemon = _daemon;
19
+ }
20
+ async fetch(_weekStart, _weekEnd) {
21
+ // Disabled for v1 — MCAS proxy blocks all standard auth patterns.
22
+ // Returns null so the journal renders without meeting data.
23
+ return null;
24
+ }
25
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * TimeTracking data source — fetches hours via commandGarden daemon.
3
+ * Ported from dashboard/api/src/journal/sources/timetracking.source.ts.
4
+ *
5
+ * Replaces OpencliService (CLI spawn) with DaemonClient (HTTP to daemon).
6
+ * The daemon runs the timetracking/report connector via browser automation.
7
+ * Returns null gracefully when the connector isn't available (Phase 2 dependency).
8
+ */
9
+ import type { DaemonClient } from '@commandgarden/shared';
10
+ import type { TimetrackingData } from '../journal.types.js';
11
+ import type { JournalConfig } from '../journal.config.js';
12
+ export declare class TimetrackingSource {
13
+ private readonly daemon;
14
+ private readonly config;
15
+ constructor(daemon: DaemonClient, config: JournalConfig);
16
+ fetch(weekStart: string, weekEnd: string): Promise<TimetrackingData | null>;
17
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * TimeTracking data source — fetches hours via commandGarden daemon.
3
+ * Ported from dashboard/api/src/journal/sources/timetracking.source.ts.
4
+ *
5
+ * Replaces OpencliService (CLI spawn) with DaemonClient (HTTP to daemon).
6
+ * The daemon runs the timetracking/report connector via browser automation.
7
+ * Returns null gracefully when the connector isn't available (Phase 2 dependency).
8
+ */
9
+ export class TimetrackingSource {
10
+ daemon;
11
+ config;
12
+ constructor(daemon, config) {
13
+ this.daemon = daemon;
14
+ this.config = config;
15
+ }
16
+ async fetch(weekStart, weekEnd) {
17
+ try {
18
+ // Determine which month(s) the week spans
19
+ const startMonth = weekStart.slice(0, 7);
20
+ const endMonth = weekEnd.slice(0, 7);
21
+ const months = startMonth === endMonth ? [startMonth] : [startMonth, endMonth];
22
+ // Fetch each month's report via daemon
23
+ const allRows = [];
24
+ for (const month of months) {
25
+ const result = await this.daemon.post('/api/run', {
26
+ connector: 'timetracking/report',
27
+ args: { month },
28
+ });
29
+ if (result.ok && result.data) {
30
+ allRows.push(...result.data);
31
+ }
32
+ }
33
+ // Filter to rows within the week range
34
+ const weekRows = allRows.filter((r) => {
35
+ if (!r.date)
36
+ return false;
37
+ return r.date >= weekStart && r.date <= weekEnd;
38
+ });
39
+ // Group by date
40
+ const dailyMap = new Map();
41
+ for (const row of weekRows) {
42
+ const date = row.date;
43
+ const entry = dailyMap.get(date) ?? { date, hours: 0, projects: [] };
44
+ const hrs = typeof row.hours === 'number' ? row.hours : 0;
45
+ entry.hours += hrs;
46
+ if (row.projectId) {
47
+ const proj = entry.projects.find((p) => p.projectId === row.projectId);
48
+ if (proj)
49
+ proj.hours += hrs;
50
+ else
51
+ entry.projects.push({ projectId: row.projectId, hours: hrs });
52
+ }
53
+ dailyMap.set(date, entry);
54
+ }
55
+ const daily = [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date));
56
+ const totalHours = Math.round(daily.reduce((sum, d) => sum + d.hours, 0) * 100) / 100;
57
+ // Find gaps: weekdays in range with 0 hours
58
+ const gaps = [];
59
+ const cursor = new Date(weekStart);
60
+ const end = new Date(weekEnd);
61
+ while (cursor <= end) {
62
+ const day = cursor.getDay();
63
+ if (day >= 1 && day <= 5) {
64
+ const iso = cursor.toISOString().slice(0, 10);
65
+ const entry = dailyMap.get(iso);
66
+ if (!entry || entry.hours === 0)
67
+ gaps.push(iso);
68
+ }
69
+ cursor.setDate(cursor.getDate() + 1);
70
+ }
71
+ return {
72
+ totalHours,
73
+ targetHours: this.config.targetHours,
74
+ daily,
75
+ gaps,
76
+ };
77
+ }
78
+ catch {
79
+ // Graceful degradation: connector not available yet (Phase 2 dependency)
80
+ return null;
81
+ }
82
+ }
83
+ }
@@ -6,6 +6,7 @@ import { configRoutes } from './config.js';
6
6
  import { preferencesRoutes } from './preferences.js';
7
7
  import { goalsRoutes } from './goals.js';
8
8
  import { timetrackingCacheRoutes } from './timetracking-cache.js';
9
+ import { journalRoutes } from './journal.js';
9
10
  export function registerRoutes(app, daemon, store) {
10
11
  statusRoutes(app, daemon);
11
12
  connectorRoutes(app, daemon);
@@ -15,4 +16,5 @@ export function registerRoutes(app, daemon, store) {
15
16
  preferencesRoutes(app, store);
16
17
  goalsRoutes(app, store);
17
18
  timetrackingCacheRoutes(app, store);
19
+ journalRoutes(app, daemon, store);
18
20
  }