@bakers-build/addon-track 0.2.1-next.211 → 0.3.1-next.308

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.
@@ -5,7 +5,13 @@ type Selection = {
5
5
  to: string;
6
6
  timezone?: string;
7
7
  };
8
- export declare function loadEvidence(client: Client, selection: Selection, worklogClient: Pick<Client, 'get'>): Promise<{
8
+ /** The slice of `@dropsh/plugin-tool`'s ToolClient this reader needs. */
9
+ export type WorklogTool = {
10
+ call(name: string, args: Record<string, unknown>): Promise<{
11
+ structuredContent?: unknown;
12
+ }>;
13
+ };
14
+ export declare function loadEvidence(client: Client, selection: Selection, worklogTool: WorklogTool): Promise<{
9
15
  owner_id: string;
10
16
  timezone: string;
11
17
  from: string;
@@ -18,14 +18,14 @@ function rows(value) {
18
18
  typeof row.id === 'string' &&
19
19
  typeof row.type === 'string'));
20
20
  }
21
- async function collect(client, type, params, path = `${type}/${type}`) {
21
+ async function collect(client, type, params) {
22
22
  const data = new Map();
23
23
  const included = new Map();
24
24
  let complete = true;
25
25
  let offset = 0;
26
26
  while (true) {
27
27
  params.addPageLimit(50).addPageOffset(offset);
28
- const raw = await client.get(path, params);
28
+ const raw = await client.get(`${type}/${type}`, params);
29
29
  if (!raw || typeof raw !== 'object' || !('data' in raw) || !rows(raw.data))
30
30
  throw new Error(`Invalid ${type} collection response.`);
31
31
  const page = raw;
@@ -62,13 +62,52 @@ async function collect(client, type, params, path = `${type}/${type}`) {
62
62
  complete,
63
63
  };
64
64
  }
65
+ const ided = (value) => Array.isArray(value) &&
66
+ value.every((row) => row && typeof row === 'object' && typeof row.id === 'string');
67
+ /** Page `tool_api__bakers_worklog_list` (the owner's own worklogs) until next_offset is null. */
68
+ async function collectWorklogs(tool, from, to) {
69
+ const data = new Map();
70
+ const tickets = new Map();
71
+ const projects = new Map();
72
+ let complete = true;
73
+ let offset = 0;
74
+ while (true) {
75
+ const { structuredContent: page } = await tool.call('tool_api__bakers_worklog_list', { from, to, limit: 50, offset });
76
+ if (!page ||
77
+ typeof page !== 'object' ||
78
+ !ided(page.data) ||
79
+ !ided(page.tickets) ||
80
+ !ided(page.projects))
81
+ throw new Error('Invalid worklog tool response.');
82
+ const { data: rows, omitted, next_offset: next } = page;
83
+ if (omitted)
84
+ complete = false;
85
+ for (const row of rows)
86
+ data.set(row.id, row);
87
+ for (const row of page.tickets)
88
+ tickets.set(row.id, row);
89
+ for (const row of page.projects)
90
+ projects.set(row.id, row);
91
+ if (next === null || next === undefined)
92
+ break;
93
+ if (!Number.isSafeInteger(next) || next <= offset)
94
+ throw new Error('Non-advancing worklog pagination.');
95
+ offset = next;
96
+ }
97
+ return {
98
+ data: [...data.values()],
99
+ tickets: [...tickets.values()],
100
+ projects: [...projects.values()],
101
+ complete,
102
+ };
103
+ }
65
104
  function context(params) {
66
105
  return params
67
106
  .addInclude(['ticket_id.project_id'])
68
107
  .addFields('bb_ticket--bb_ticket', ['identifier', 'title', 'project_id'])
69
108
  .addFields('bb_project--bb_project', ['name']);
70
109
  }
71
- export async function loadEvidence(client, selection, worklogClient) {
110
+ export async function loadEvidence(client, selection, worklogTool) {
72
111
  const dates = selectedDates(selection.from, selection.to);
73
112
  if (selection.timezone)
74
113
  calendar(selection.timezone);
@@ -131,14 +170,10 @@ export async function loadEvidence(client, selection, worklogClient) {
131
170
  ])
132
171
  .addFields('bb_comment--bb_comment', ['body'])
133
172
  .addSort('drupal_internal__id');
134
- const worklogParams = new DrupalJsonApiParams().addCustomParam({
135
- from: weekStart,
136
- to: weekEnd,
137
- });
138
173
  const [runPage, activityPage, worklogPage] = await Promise.all([
139
174
  collect(client, 'bb_run', runParams),
140
175
  collect(client, 'bb_activity', activityParams),
141
- collect(worklogClient, 'bb_track_worklog', worklogParams, 'worklogs'),
176
+ collectWorklogs(worklogTool, weekStart, weekEnd),
142
177
  ]);
143
178
  for (const [name, page] of [
144
179
  ['runs', runPage],
@@ -151,11 +186,7 @@ export async function loadEvidence(client, selection, worklogClient) {
151
186
  const tickets = {};
152
187
  const projects = {};
153
188
  const summaries = new Map();
154
- for (const row of [
155
- ...runPage.included,
156
- ...activityPage.included,
157
- ...worklogPage.included,
158
- ]) {
189
+ for (const row of [...runPage.included, ...activityPage.included]) {
159
190
  if (row.type === 'bb_ticket--bb_ticket')
160
191
  tickets[row.id] = {
161
192
  identifier: attr(row, 'identifier') ?? null,
@@ -170,6 +201,14 @@ export async function loadEvidence(client, selection, worklogClient) {
170
201
  summaries.set(row.id, body.value);
171
202
  }
172
203
  }
204
+ for (const ticket of worklogPage.tickets)
205
+ tickets[ticket.id] = {
206
+ identifier: ticket.identifier ?? null,
207
+ title: ticket.title ?? null,
208
+ project_id: ticket.project_id ?? null,
209
+ };
210
+ for (const project of worklogPage.projects)
211
+ projects[project.id] = { name: project.name ?? null };
173
212
  const unplaced = [];
174
213
  for (const [id, ticket] of Object.entries(tickets))
175
214
  if (ticket.project_id && !projects[ticket.project_id])
@@ -249,24 +288,21 @@ export async function loadEvidence(client, selection, worklogClient) {
249
288
  summary_truncated: (summary?.length ?? 0) > 2000,
250
289
  };
251
290
  });
252
- const worklogs = worklogPage.data
253
- .filter((row) => relation(row, 'owner_user_id') === owner)
254
- .map((row) => ({
291
+ const worklogs = worklogPage.data.map((row) => ({
255
292
  id: row.id,
256
- date: attr(row, 'work_date'),
257
- state: attr(row, 'state'),
258
- duration_seconds: seconds(attr(row, 'duration_seconds')),
259
- description: attr(row, 'description'),
260
- ticket_id: relation(row, 'ticket_id'),
261
- project_id: relation(row, 'project_id'),
262
- submission_id: relation(row, 'submission_id'),
263
- source_keys: attr(row, 'source_keys') ?? null,
293
+ date: row.work_date,
294
+ state: row.state,
295
+ duration_seconds: seconds(row.duration_seconds),
296
+ description: row.description,
297
+ ticket_id: row.ticket_id ?? null,
298
+ project_id: row.project_id ?? null,
299
+ submission_id: row.submission_id ?? null,
300
+ source_keys: row.source_keys ?? null,
264
301
  }));
265
- const worklogsValid = worklogPage.data.every((row) => relation(row, 'owner_user_id')) &&
266
- worklogs.every((row) => typeof row.date === 'string' &&
267
- Number.isFinite(civilDate(row.date)) &&
268
- row.duration_seconds !== null &&
269
- ['recorded', 'approved', 'proposed'].includes(String(row.state)));
302
+ const worklogsValid = worklogs.every((row) => typeof row.date === 'string' &&
303
+ Number.isFinite(civilDate(row.date)) &&
304
+ row.duration_seconds !== null &&
305
+ ['recorded', 'approved', 'proposed'].includes(String(row.state)));
270
306
  if (!worklogsValid)
271
307
  evidenceGaps.push('Some worklogs have invalid dates, states or durations.');
272
308
  for (const row of [...activities, ...worklogs]) {
package/dist/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { createBbConnectionClient, loadReadyBbConnection, } from '@bakers-build/core';
1
+ import { createBbConnectionContext, loadReadyBbConnection, } from '@bakers-build/core';
2
+ import { createToolClient } from '@dropsh/plugin-tool';
2
3
  import { loadEvidence } from './evidence.js';
3
4
  export function registerTrack(program, read) {
4
5
  program
@@ -26,12 +27,15 @@ const trackCommand = {
26
27
  ...(options.config ? { explicit: options.config } : {}),
27
28
  ...(options.connection ? { connection: options.connection } : {}),
28
29
  });
29
- const client = await createBbConnectionClient(connection);
30
- const worklogs = await createBbConnectionClient({
31
- ...connection,
32
- site: { ...connection.site, jsonapi_prefix: '/api/time' },
33
- });
34
- return loadEvidence(client, options, worklogs);
30
+ const { client, auth, baseUrl } = await createBbConnectionContext(connection);
31
+ // Worklogs come from the `tool_api__bakers_worklog_list` MCP tool on the same auth profile.
32
+ const tools = createToolClient({ auth, baseUrl, endpoint: '/mcp' });
33
+ try {
34
+ return await loadEvidence(client, options, tools);
35
+ }
36
+ finally {
37
+ await tools.close();
38
+ }
35
39
  });
36
40
  },
37
41
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakers-build/addon-track",
3
- "version": "0.2.1-next.211",
3
+ "version": "0.3.1-next.308",
4
4
  "description": "Read personal daily evidence for time preparation.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,10 +20,11 @@
20
20
  "directory": "bakers-cli/addons/track"
21
21
  },
22
22
  "dependencies": {
23
+ "@dropsh/plugin-tool": "^0.8.2",
23
24
  "commander": "^12.1.0",
24
25
  "drupal-jsonapi-params": "^3.0.1"
25
26
  },
26
27
  "peerDependencies": {
27
- "@bakers-build/core": "^0.2.1-next.211"
28
+ "@bakers-build/core": "^0.3.1-next.308"
28
29
  }
29
30
  }