@bretwardjames/ghp-core 0.6.1 → 0.7.1

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/index.cjs CHANGED
@@ -78,6 +78,7 @@ __export(index_exports, {
78
78
  findSessionFile: () => findSessionFile,
79
79
  formatAction: () => formatAction,
80
80
  formatConflict: () => formatConflict,
81
+ formatStandupText: () => formatStandupText,
81
82
  gatherDashboardData: () => gatherDashboardData,
82
83
  generateBranchName: () => generateBranchName,
83
84
  generateWorktreePath: () => generateWorktreePath,
@@ -135,6 +136,7 @@ __export(index_exports, {
135
136
  parseIssueUrl: () => parseIssueUrl,
136
137
  parseRateLimitDelay: () => parseRateLimitDelay,
137
138
  parseSessionLine: () => parseSessionLine,
139
+ parseSince: () => parseSince,
138
140
  pullLatest: () => pullLatest,
139
141
  queries: () => queries_exports,
140
142
  registerAgent: () => registerAgent,
@@ -190,12 +192,15 @@ __export(queries_exports, {
190
192
  ISSUE_FOR_UPDATE_QUERY: () => ISSUE_FOR_UPDATE_QUERY,
191
193
  ISSUE_NODE_ID_QUERY: () => ISSUE_NODE_ID_QUERY,
192
194
  ISSUE_RELATIONSHIPS_QUERY: () => ISSUE_RELATIONSHIPS_QUERY,
195
+ ISSUE_TIMELINE_QUERY: () => ISSUE_TIMELINE_QUERY,
193
196
  ISSUE_TYPES_QUERY: () => ISSUE_TYPES_QUERY,
194
197
  ISSUE_WITH_PROJECT_ITEMS_QUERY: () => ISSUE_WITH_PROJECT_ITEMS_QUERY,
195
198
  LABEL_EXISTS_QUERY: () => LABEL_EXISTS_QUERY,
196
199
  PROJECT_FIELDS_QUERY: () => PROJECT_FIELDS_QUERY,
197
200
  PROJECT_ITEMS_QUERY: () => PROJECT_ITEMS_QUERY,
198
201
  PROJECT_VIEWS_QUERY: () => PROJECT_VIEWS_QUERY,
202
+ PR_AUTHORED_SEARCH_QUERY: () => PR_AUTHORED_SEARCH_QUERY,
203
+ PR_REVIEWS_SEARCH_QUERY: () => PR_REVIEWS_SEARCH_QUERY,
199
204
  RECENT_ISSUES_QUERY: () => RECENT_ISSUES_QUERY,
200
205
  REMOVE_BLOCKED_BY_MUTATION: () => REMOVE_BLOCKED_BY_MUTATION,
201
206
  REMOVE_LABELS_MUTATION: () => REMOVE_LABELS_MUTATION,
@@ -280,6 +285,7 @@ var PROJECT_ITEMS_QUERY = `
280
285
  number
281
286
  url
282
287
  state
288
+ updatedAt
283
289
  issueType { name }
284
290
  assignees(first: 5) { nodes { login } }
285
291
  labels(first: 10) { nodes { name color } }
@@ -294,6 +300,7 @@ var PROJECT_ITEMS_QUERY = `
294
300
  number
295
301
  url
296
302
  state
303
+ updatedAt
297
304
  merged
298
305
  assignees(first: 5) { nodes { login } }
299
306
  labels(first: 10) { nodes { name color } }
@@ -657,6 +664,162 @@ var ISSUE_WITH_PROJECT_ITEMS_QUERY = `
657
664
  }
658
665
  }
659
666
  `;
667
+ var ISSUE_TIMELINE_QUERY = `
668
+ query($owner: String!, $name: String!, $number: Int!, $since: DateTime!) {
669
+ repository(owner: $owner, name: $name) {
670
+ issueOrPullRequest(number: $number) {
671
+ ... on Issue {
672
+ timelineItems(first: 100, since: $since) {
673
+ nodes {
674
+ __typename
675
+ ... on IssueComment {
676
+ author { login }
677
+ createdAt
678
+ body
679
+ }
680
+ ... on LabeledEvent {
681
+ actor { login }
682
+ createdAt
683
+ label { name }
684
+ }
685
+ ... on UnlabeledEvent {
686
+ actor { login }
687
+ createdAt
688
+ label { name }
689
+ }
690
+ ... on AssignedEvent {
691
+ actor { login }
692
+ createdAt
693
+ assignee { ... on User { login } }
694
+ }
695
+ ... on UnassignedEvent {
696
+ actor { login }
697
+ createdAt
698
+ assignee { ... on User { login } }
699
+ }
700
+ ... on ClosedEvent {
701
+ actor { login }
702
+ createdAt
703
+ }
704
+ ... on ReopenedEvent {
705
+ actor { login }
706
+ createdAt
707
+ }
708
+ ... on CrossReferencedEvent {
709
+ actor { login }
710
+ createdAt
711
+ source {
712
+ __typename
713
+ ... on PullRequest { number title url }
714
+ ... on Issue { number title }
715
+ }
716
+ }
717
+ }
718
+ }
719
+ }
720
+ ... on PullRequest {
721
+ timelineItems(first: 100, since: $since) {
722
+ nodes {
723
+ __typename
724
+ ... on IssueComment {
725
+ author { login }
726
+ createdAt
727
+ body
728
+ }
729
+ ... on LabeledEvent {
730
+ actor { login }
731
+ createdAt
732
+ label { name }
733
+ }
734
+ ... on UnlabeledEvent {
735
+ actor { login }
736
+ createdAt
737
+ label { name }
738
+ }
739
+ ... on AssignedEvent {
740
+ actor { login }
741
+ createdAt
742
+ assignee { ... on User { login } }
743
+ }
744
+ ... on UnassignedEvent {
745
+ actor { login }
746
+ createdAt
747
+ assignee { ... on User { login } }
748
+ }
749
+ ... on ClosedEvent {
750
+ actor { login }
751
+ createdAt
752
+ }
753
+ ... on ReopenedEvent {
754
+ actor { login }
755
+ createdAt
756
+ }
757
+ ... on CrossReferencedEvent {
758
+ actor { login }
759
+ createdAt
760
+ source {
761
+ __typename
762
+ ... on PullRequest { number title url }
763
+ ... on Issue { number title }
764
+ }
765
+ }
766
+ ... on PullRequestReview {
767
+ author { login }
768
+ createdAt
769
+ state
770
+ }
771
+ ... on ReviewRequestedEvent {
772
+ actor { login }
773
+ createdAt
774
+ requestedReviewer {
775
+ ... on User { login }
776
+ }
777
+ }
778
+ }
779
+ }
780
+ }
781
+ }
782
+ }
783
+ }
784
+ `;
785
+ var PR_REVIEWS_SEARCH_QUERY = `
786
+ query($searchQuery: String!) {
787
+ search(query: $searchQuery, type: ISSUE, first: 50) {
788
+ nodes {
789
+ ... on PullRequest {
790
+ number
791
+ title
792
+ url
793
+ author { login }
794
+ reviews(first: 20) {
795
+ nodes {
796
+ author { login }
797
+ state
798
+ submittedAt
799
+ }
800
+ }
801
+ }
802
+ }
803
+ }
804
+ }
805
+ `;
806
+ var PR_AUTHORED_SEARCH_QUERY = `
807
+ query($searchQuery: String!) {
808
+ search(query: $searchQuery, type: ISSUE, first: 50) {
809
+ nodes {
810
+ ... on PullRequest {
811
+ number
812
+ title
813
+ url
814
+ state
815
+ createdAt
816
+ mergedAt
817
+ mergedBy { login }
818
+ }
819
+ }
820
+ }
821
+ }
822
+ `;
660
823
  var ISSUE_RELATIONSHIPS_QUERY = `
661
824
  query($owner: String!, $name: String!, $number: Int!) {
662
825
  repository(owner: $owner, name: $name) {
@@ -1062,7 +1225,8 @@ var GitHubAPI = class {
1062
1225
  parent,
1063
1226
  subIssues,
1064
1227
  blockedBy,
1065
- blocking
1228
+ blocking,
1229
+ updatedAt: content.updatedAt || null
1066
1230
  };
1067
1231
  });
1068
1232
  }
@@ -1171,7 +1335,8 @@ var GitHubAPI = class {
1171
1335
  parent: issue.parent,
1172
1336
  subIssues: issue.subIssues.nodes,
1173
1337
  blockedBy: issue.blockedBy.nodes,
1174
- blocking: issue.blocking.nodes
1338
+ blocking: issue.blocking.nodes,
1339
+ updatedAt: null
1175
1340
  };
1176
1341
  } catch (error) {
1177
1342
  return null;
@@ -1733,6 +1898,279 @@ var GitHubAPI = class {
1733
1898
  return null;
1734
1899
  }
1735
1900
  }
1901
+ /**
1902
+ * Get recent activity across all project items since a given time.
1903
+ * Multi-pass approach:
1904
+ * Pass 1: Fetch all project items, filter by updatedAt client-side
1905
+ * Pass 2: Fetch timeline events only for items that changed
1906
+ * Pass 3: Search for PRs reviewed by user (not captured in project items)
1907
+ * Pass 4: Search for PRs authored by user (created/merged)
1908
+ */
1909
+ async getRecentActivity(repo, since, options) {
1910
+ if (!this.graphqlWithAuth) throw new Error("Not authenticated");
1911
+ const projects = await this.getProjects(repo);
1912
+ if (projects.length === 0) return [];
1913
+ const allItems = [];
1914
+ for (const project of projects) {
1915
+ const items = await this.getProjectItems(project.id, project.title);
1916
+ allItems.push(...items);
1917
+ }
1918
+ const sinceMs = since.getTime();
1919
+ const sinceISO = since.toISOString();
1920
+ const sinceDate = sinceISO.split("T")[0];
1921
+ let recentItems = allItems.filter(
1922
+ (item) => item.updatedAt && new Date(item.updatedAt).getTime() >= sinceMs
1923
+ );
1924
+ if (options?.mine && this.username) {
1925
+ recentItems = recentItems.filter(
1926
+ (item) => item.assignees.includes(this.username)
1927
+ );
1928
+ }
1929
+ const seen = /* @__PURE__ */ new Set();
1930
+ recentItems = recentItems.filter((item) => {
1931
+ const key = `${item.repository || ""}#${item.number}`;
1932
+ if (!item.number || seen.has(key)) return false;
1933
+ seen.add(key);
1934
+ return true;
1935
+ });
1936
+ const activities = [];
1937
+ const BATCH_SIZE = 5;
1938
+ for (let i = 0; i < recentItems.length; i += BATCH_SIZE) {
1939
+ const batch = recentItems.slice(i, i + BATCH_SIZE);
1940
+ const results = await Promise.allSettled(
1941
+ batch.map(async (item) => {
1942
+ if (!item.number || !item.repository) return null;
1943
+ const [owner, name] = item.repository.split("/");
1944
+ if (!owner || !name) return null;
1945
+ const events = await this.fetchTimelineEvents(
1946
+ owner,
1947
+ name,
1948
+ item.number,
1949
+ sinceISO
1950
+ );
1951
+ if (events.length === 0) return null;
1952
+ return {
1953
+ issue: {
1954
+ number: item.number,
1955
+ title: item.title,
1956
+ url: item.url || ""
1957
+ },
1958
+ status: item.status,
1959
+ assignees: item.assignees,
1960
+ changes: events
1961
+ };
1962
+ })
1963
+ );
1964
+ for (const result of results) {
1965
+ if (result.status === "fulfilled" && result.value) {
1966
+ activities.push(result.value);
1967
+ }
1968
+ }
1969
+ }
1970
+ const activityNumbers = new Set(activities.map((a) => a.issue.number));
1971
+ if (this.username) {
1972
+ const reviewActivities = await this.fetchReviewedPRs(
1973
+ repo,
1974
+ this.username,
1975
+ sinceDate,
1976
+ sinceMs,
1977
+ activityNumbers
1978
+ );
1979
+ for (const a of reviewActivities) {
1980
+ activityNumbers.add(a.issue.number);
1981
+ activities.push(a);
1982
+ }
1983
+ }
1984
+ if (this.username) {
1985
+ const authoredActivities = await this.fetchAuthoredPRs(
1986
+ repo,
1987
+ this.username,
1988
+ sinceDate,
1989
+ sinceMs,
1990
+ activityNumbers
1991
+ );
1992
+ for (const a of authoredActivities) {
1993
+ activityNumbers.add(a.issue.number);
1994
+ activities.push(a);
1995
+ }
1996
+ }
1997
+ activities.sort((a, b) => {
1998
+ const aLatest = a.changes[a.changes.length - 1]?.timestamp || "";
1999
+ const bLatest = b.changes[b.changes.length - 1]?.timestamp || "";
2000
+ return bLatest.localeCompare(aLatest);
2001
+ });
2002
+ return activities;
2003
+ }
2004
+ /**
2005
+ * Search for PRs the user reviewed that aren't already in the activity list.
2006
+ */
2007
+ async fetchReviewedPRs(repo, username, sinceDate, sinceMs, seen) {
2008
+ const searchQuery = `repo:${repo.owner}/${repo.name} reviewed-by:${username} updated:>=${sinceDate} is:pr`;
2009
+ const response = await this.graphqlWithRetry(
2010
+ PR_REVIEWS_SEARCH_QUERY,
2011
+ { searchQuery }
2012
+ );
2013
+ const activities = [];
2014
+ for (const pr of response.search.nodes) {
2015
+ if (seen.has(pr.number)) continue;
2016
+ const recentReviews = pr.reviews.nodes.filter(
2017
+ (r) => r.author?.login === username && new Date(r.submittedAt).getTime() >= sinceMs
2018
+ );
2019
+ if (recentReviews.length === 0) continue;
2020
+ const events = recentReviews.map((r) => {
2021
+ const stateLabel = r.state === "APPROVED" ? "Approved" : r.state === "CHANGES_REQUESTED" ? "Changes requested" : r.state === "COMMENTED" ? "Review comment" : r.state === "DISMISSED" ? "Review dismissed" : r.state;
2022
+ return {
2023
+ type: "review_submitted",
2024
+ actor: username,
2025
+ timestamp: r.submittedAt,
2026
+ details: stateLabel
2027
+ };
2028
+ });
2029
+ activities.push({
2030
+ issue: { number: pr.number, title: pr.title, url: pr.url },
2031
+ status: null,
2032
+ assignees: [],
2033
+ changes: events
2034
+ });
2035
+ }
2036
+ return activities;
2037
+ }
2038
+ /**
2039
+ * Search for PRs the user authored that aren't already in the activity list.
2040
+ * Captures PR creation and merges.
2041
+ */
2042
+ async fetchAuthoredPRs(repo, username, sinceDate, sinceMs, seen) {
2043
+ const searchQuery = `repo:${repo.owner}/${repo.name} author:${username} created:>=${sinceDate} is:pr`;
2044
+ const response = await this.graphqlWithRetry(
2045
+ PR_AUTHORED_SEARCH_QUERY,
2046
+ { searchQuery }
2047
+ );
2048
+ const activities = [];
2049
+ for (const pr of response.search.nodes) {
2050
+ if (seen.has(pr.number)) continue;
2051
+ const events = [];
2052
+ if (new Date(pr.createdAt).getTime() >= sinceMs) {
2053
+ events.push({
2054
+ type: "pr_created",
2055
+ actor: username,
2056
+ timestamp: pr.createdAt
2057
+ });
2058
+ }
2059
+ if (pr.mergedAt && new Date(pr.mergedAt).getTime() >= sinceMs) {
2060
+ events.push({
2061
+ type: "pr_merged",
2062
+ actor: pr.mergedBy?.login || username,
2063
+ timestamp: pr.mergedAt
2064
+ });
2065
+ }
2066
+ if (events.length === 0) continue;
2067
+ activities.push({
2068
+ issue: { number: pr.number, title: pr.title, url: pr.url },
2069
+ status: pr.state === "MERGED" ? "Merged" : pr.state === "OPEN" ? "Open" : "Closed",
2070
+ assignees: [username],
2071
+ changes: events
2072
+ });
2073
+ }
2074
+ return activities;
2075
+ }
2076
+ /**
2077
+ * Fetch and normalize timeline events for a single issue/PR
2078
+ */
2079
+ async fetchTimelineEvents(owner, name, number, since) {
2080
+ if (!this.graphqlWithAuth) throw new Error("Not authenticated");
2081
+ const response = await this.graphqlWithRetry(
2082
+ ISSUE_TIMELINE_QUERY,
2083
+ { owner, name, number, since }
2084
+ );
2085
+ const item = response.repository.issueOrPullRequest;
2086
+ if (!item) return [];
2087
+ const events = [];
2088
+ for (const node of item.timelineItems.nodes) {
2089
+ const actor = node.actor?.login || node.author?.login || "unknown";
2090
+ const timestamp = node.createdAt || "";
2091
+ switch (node.__typename) {
2092
+ case "IssueComment":
2093
+ events.push({
2094
+ type: "comment",
2095
+ actor,
2096
+ timestamp,
2097
+ details: node.body ? node.body.substring(0, 80) + (node.body.length > 80 ? "..." : "") : void 0
2098
+ });
2099
+ break;
2100
+ case "LabeledEvent":
2101
+ events.push({
2102
+ type: "labeled",
2103
+ actor,
2104
+ timestamp,
2105
+ details: node.label?.name
2106
+ });
2107
+ break;
2108
+ case "UnlabeledEvent":
2109
+ events.push({
2110
+ type: "unlabeled",
2111
+ actor,
2112
+ timestamp,
2113
+ details: node.label?.name
2114
+ });
2115
+ break;
2116
+ case "AssignedEvent":
2117
+ events.push({
2118
+ type: "assigned",
2119
+ actor,
2120
+ timestamp,
2121
+ details: node.assignee?.login
2122
+ });
2123
+ break;
2124
+ case "UnassignedEvent":
2125
+ events.push({
2126
+ type: "unassigned",
2127
+ actor,
2128
+ timestamp,
2129
+ details: node.assignee?.login
2130
+ });
2131
+ break;
2132
+ case "ClosedEvent":
2133
+ events.push({ type: "closed", actor, timestamp });
2134
+ break;
2135
+ case "ReopenedEvent":
2136
+ events.push({ type: "reopened", actor, timestamp });
2137
+ break;
2138
+ case "CrossReferencedEvent": {
2139
+ const source = node.source;
2140
+ if (source) {
2141
+ const ref = source.__typename === "PullRequest" ? `PR #${source.number}: ${source.title}` : `#${source.number}: ${source.title}`;
2142
+ events.push({
2143
+ type: "referenced",
2144
+ actor,
2145
+ timestamp,
2146
+ details: ref
2147
+ });
2148
+ }
2149
+ break;
2150
+ }
2151
+ case "PullRequestReview": {
2152
+ const stateLabel = node.state === "APPROVED" ? "Approved" : node.state === "CHANGES_REQUESTED" ? "Changes requested" : node.state === "COMMENTED" ? "Review comment" : node.state === "DISMISSED" ? "Review dismissed" : node.state || "Reviewed";
2153
+ events.push({
2154
+ type: "review_submitted",
2155
+ actor,
2156
+ timestamp,
2157
+ details: stateLabel
2158
+ });
2159
+ break;
2160
+ }
2161
+ case "ReviewRequestedEvent": {
2162
+ events.push({
2163
+ type: "review_requested",
2164
+ actor,
2165
+ timestamp,
2166
+ details: node.requestedReviewer?.login
2167
+ });
2168
+ break;
2169
+ }
2170
+ }
2171
+ }
2172
+ return events;
2173
+ }
1736
2174
  };
1737
2175
 
1738
2176
  // src/branch-linker.ts
@@ -3804,6 +4242,114 @@ function validateUrl(url) {
3804
4242
  return url;
3805
4243
  }
3806
4244
 
4245
+ // src/standup.ts
4246
+ function formatStandupText(activities, options) {
4247
+ const { since } = options;
4248
+ const lines = [];
4249
+ const sinceStr = formatRelativeDate(since);
4250
+ const issueCount = activities.length;
4251
+ lines.push(`Since ${sinceStr} \u2014 ${issueCount} issue${issueCount !== 1 ? "s" : ""} changed`);
4252
+ lines.push("");
4253
+ if (activities.length === 0) {
4254
+ lines.push("No activity found in this time window.");
4255
+ return lines.join("\n");
4256
+ }
4257
+ for (const activity of activities) {
4258
+ const statusTag = activity.status ? ` [${activity.status}]` : "";
4259
+ lines.push(`#${activity.issue.number} ${activity.issue.title}${statusTag}`);
4260
+ for (const event of activity.changes) {
4261
+ lines.push(` ${formatEventLine(event)}`);
4262
+ }
4263
+ lines.push("");
4264
+ }
4265
+ return lines.join("\n").trimEnd();
4266
+ }
4267
+ function parseSince(input) {
4268
+ const isoDate = new Date(input);
4269
+ if (!isNaN(isoDate.getTime()) && input.includes("-")) {
4270
+ return isoDate;
4271
+ }
4272
+ const match = input.match(/^(\d+)\s*(h|d|w)$/i);
4273
+ if (!match) {
4274
+ throw new Error(`Invalid duration format: "${input}". Use formats like "24h", "2d", "1w", or an ISO date.`);
4275
+ }
4276
+ const amount = parseInt(match[1], 10);
4277
+ const unit = match[2].toLowerCase();
4278
+ const now = /* @__PURE__ */ new Date();
4279
+ switch (unit) {
4280
+ case "h":
4281
+ return new Date(now.getTime() - amount * 60 * 60 * 1e3);
4282
+ case "d":
4283
+ return new Date(now.getTime() - amount * 24 * 60 * 60 * 1e3);
4284
+ case "w":
4285
+ return new Date(now.getTime() - amount * 7 * 24 * 60 * 60 * 1e3);
4286
+ default:
4287
+ throw new Error(`Unknown duration unit: "${unit}"`);
4288
+ }
4289
+ }
4290
+ function formatEventLine(event) {
4291
+ const arrow = "\u2197";
4292
+ const timestamp = formatShortTimestamp(event.timestamp);
4293
+ const actor = event.actor;
4294
+ switch (event.type) {
4295
+ case "comment":
4296
+ return `${arrow} Comment by ${actor} (${timestamp})${event.details ? ": " + event.details : ""}`;
4297
+ case "labeled":
4298
+ return `${arrow} Labeled "${event.details}" by ${actor} (${timestamp})`;
4299
+ case "unlabeled":
4300
+ return `${arrow} Unlabeled "${event.details}" by ${actor} (${timestamp})`;
4301
+ case "assigned":
4302
+ return `${arrow} Assigned to ${event.details || actor} (${timestamp})`;
4303
+ case "unassigned":
4304
+ return `${arrow} Unassigned ${event.details || ""} by ${actor} (${timestamp})`;
4305
+ case "closed":
4306
+ return `${arrow} Closed by ${actor} (${timestamp})`;
4307
+ case "reopened":
4308
+ return `${arrow} Reopened by ${actor} (${timestamp})`;
4309
+ case "referenced":
4310
+ return `${arrow} ${event.details} linked by ${actor} (${timestamp})`;
4311
+ case "review_submitted":
4312
+ return `${arrow} ${event.details} by ${actor} (${timestamp})`;
4313
+ case "review_requested":
4314
+ return `${arrow} Review requested from ${event.details || "team"} by ${actor} (${timestamp})`;
4315
+ case "pr_created":
4316
+ return `${arrow} PR created by ${actor} (${timestamp})`;
4317
+ case "pr_merged":
4318
+ return `${arrow} PR merged by ${actor} (${timestamp})`;
4319
+ default:
4320
+ return `${arrow} ${event.type} by ${actor} (${timestamp})`;
4321
+ }
4322
+ }
4323
+ function formatRelativeDate(date) {
4324
+ const now = /* @__PURE__ */ new Date();
4325
+ const diffMs = now.getTime() - date.getTime();
4326
+ const diffHours = Math.round(diffMs / (1e3 * 60 * 60));
4327
+ const dateStr = date.toLocaleDateString("en-US", {
4328
+ month: "short",
4329
+ day: "numeric",
4330
+ hour: "2-digit",
4331
+ minute: "2-digit"
4332
+ });
4333
+ if (diffHours < 24) {
4334
+ return `${diffHours}h ago (${dateStr})`;
4335
+ } else if (diffHours < 48) {
4336
+ return `yesterday (${dateStr})`;
4337
+ } else {
4338
+ const diffDays = Math.round(diffHours / 24);
4339
+ return `${diffDays} days ago (${dateStr})`;
4340
+ }
4341
+ }
4342
+ function formatShortTimestamp(isoTimestamp) {
4343
+ if (!isoTimestamp) return "";
4344
+ const date = new Date(isoTimestamp);
4345
+ return date.toLocaleDateString("en-US", {
4346
+ month: "short",
4347
+ day: "numeric",
4348
+ hour: "2-digit",
4349
+ minute: "2-digit"
4350
+ });
4351
+ }
4352
+
3807
4353
  // src/dashboard/index.ts
3808
4354
  var import_child_process3 = require("child_process");
3809
4355
  var import_util3 = require("util");
@@ -5261,6 +5807,7 @@ EOF
5261
5807
  findSessionFile,
5262
5808
  formatAction,
5263
5809
  formatConflict,
5810
+ formatStandupText,
5264
5811
  gatherDashboardData,
5265
5812
  generateBranchName,
5266
5813
  generateWorktreePath,
@@ -5318,6 +5865,7 @@ EOF
5318
5865
  parseIssueUrl,
5319
5866
  parseRateLimitDelay,
5320
5867
  parseSessionLine,
5868
+ parseSince,
5321
5869
  pullLatest,
5322
5870
  queries,
5323
5871
  registerAgent,