@knpkv/jira-clockify 0.2.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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/dist/package.json +47 -0
  4. package/dist/src/bin.js +28 -0
  5. package/dist/src/bin.js.map +1 -0
  6. package/dist/src/cli/auth.js +169 -0
  7. package/dist/src/cli/auth.js.map +1 -0
  8. package/dist/src/cli/config.js +107 -0
  9. package/dist/src/cli/config.js.map +1 -0
  10. package/dist/src/cli/fuzzySelect.js +149 -0
  11. package/dist/src/cli/fuzzySelect.js.map +1 -0
  12. package/dist/src/cli/layers.js +59 -0
  13. package/dist/src/cli/layers.js.map +1 -0
  14. package/dist/src/cli/list.js +27 -0
  15. package/dist/src/cli/list.js.map +1 -0
  16. package/dist/src/cli/setup.js +134 -0
  17. package/dist/src/cli/setup.js.map +1 -0
  18. package/dist/src/cli/timer/discard.js +33 -0
  19. package/dist/src/cli/timer/discard.js.map +1 -0
  20. package/dist/src/cli/timer/edit.js +139 -0
  21. package/dist/src/cli/timer/edit.js.map +1 -0
  22. package/dist/src/cli/timer/index.js +12 -0
  23. package/dist/src/cli/timer/index.js.map +1 -0
  24. package/dist/src/cli/timer/log.js +96 -0
  25. package/dist/src/cli/timer/log.js.map +1 -0
  26. package/dist/src/cli/timer/start.js +156 -0
  27. package/dist/src/cli/timer/start.js.map +1 -0
  28. package/dist/src/cli/timer/status.js +80 -0
  29. package/dist/src/cli/timer/status.js.map +1 -0
  30. package/dist/src/cli/timer/stop.js +96 -0
  31. package/dist/src/cli/timer/stop.js.map +1 -0
  32. package/dist/src/cli/timer.js +7 -0
  33. package/dist/src/cli/timer.js.map +1 -0
  34. package/dist/src/main.js +24 -0
  35. package/dist/src/main.js.map +1 -0
  36. package/dist/src/services/ClockifyAuth.js +77 -0
  37. package/dist/src/services/ClockifyAuth.js.map +1 -0
  38. package/dist/src/services/ConfigService.js +66 -0
  39. package/dist/src/services/ConfigService.js.map +1 -0
  40. package/dist/src/services/StateWriter.js +69 -0
  41. package/dist/src/services/StateWriter.js.map +1 -0
  42. package/dist/src/services/TicketService.js +106 -0
  43. package/dist/src/services/TicketService.js.map +1 -0
  44. package/dist/src/services/TimerService.js +271 -0
  45. package/dist/src/services/TimerService.js.map +1 -0
  46. package/dist/src/tui/App.js +204 -0
  47. package/dist/src/tui/App.js.map +1 -0
  48. package/dist/src/tui/atoms/runtime.js +9 -0
  49. package/dist/src/tui/atoms/runtime.js.map +1 -0
  50. package/dist/src/tui/atoms/tickets.js +17 -0
  51. package/dist/src/tui/atoms/tickets.js.map +1 -0
  52. package/dist/src/tui/atoms/timer.js +31 -0
  53. package/dist/src/tui/atoms/timer.js.map +1 -0
  54. package/dist/src/tui/atoms/ui.js +10 -0
  55. package/dist/src/tui/atoms/ui.js.map +1 -0
  56. package/dist/src/tui/components/BigTimer.js +60 -0
  57. package/dist/src/tui/components/BigTimer.js.map +1 -0
  58. package/dist/src/tui/components/Footer.js +16 -0
  59. package/dist/src/tui/components/Footer.js.map +1 -0
  60. package/dist/src/tui/components/Header.js +16 -0
  61. package/dist/src/tui/components/Header.js.map +1 -0
  62. package/dist/src/tui/components/PopupInput.js +30 -0
  63. package/dist/src/tui/components/PopupInput.js.map +1 -0
  64. package/dist/src/tui/components/PopupMessage.js +47 -0
  65. package/dist/src/tui/components/PopupMessage.js.map +1 -0
  66. package/dist/src/tui/components/TicketList.js +40 -0
  67. package/dist/src/tui/components/TicketList.js.map +1 -0
  68. package/dist/src/tui/components/TicketRow.js +48 -0
  69. package/dist/src/tui/components/TicketRow.js.map +1 -0
  70. package/dist/src/tui/components/TimerDisplay.js +30 -0
  71. package/dist/src/tui/components/TimerDisplay.js.map +1 -0
  72. package/dist/src/tui/components/index.js +12 -0
  73. package/dist/src/tui/components/index.js.map +1 -0
  74. package/dist/src/tui/context/theme.js +15 -0
  75. package/dist/src/tui/context/theme.js.map +1 -0
  76. package/dist/src/tui/hooks/useElapsedTimer.js +35 -0
  77. package/dist/src/tui/hooks/useElapsedTimer.js.map +1 -0
  78. package/dist/src/tui/hooks/useTerminalSize.js +32 -0
  79. package/dist/src/tui/hooks/useTerminalSize.js.map +1 -0
  80. package/dist/src/utils/time.js +23 -0
  81. package/dist/src/utils/time.js.map +1 -0
  82. package/dist/test/TimerService.test.js +355 -0
  83. package/dist/test/TimerService.test.js.map +1 -0
  84. package/dist/tsconfig.tsbuildinfo +1 -0
  85. package/nvim/lua/jcf/branch.lua +26 -0
  86. package/nvim/lua/jcf/float.lua +87 -0
  87. package/nvim/lua/jcf/init.lua +70 -0
  88. package/nvim/lua/jcf/state.lua +56 -0
  89. package/nvim/lua/jcf/statusline.lua +31 -0
  90. package/nvim/lua/jcf/telescope.lua +50 -0
  91. package/nvim/plugin/jcf.vim +2 -0
  92. package/package.json +47 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * User configuration persistence for jcf defaults (JQL, project, billable).
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **File-backed with defaults**: Reads `~/.jcf/config.json`, merging stored values over
7
+ * {@link defaultConfig}. Missing or corrupt files silently fall back to defaults.
8
+ * - **Partial updates**: {@link ConfigServiceShape.set} merges a patch over the current config.
9
+ *
10
+ * @module
11
+ */
12
+ import * as FileSystem from "@effect/platform/FileSystem";
13
+ import * as Path from "@effect/platform/Path";
14
+ import * as Context from "effect/Context";
15
+ import * as Effect from "effect/Effect";
16
+ import * as Layer from "effect/Layer";
17
+ import { homedir } from "node:os";
18
+ const defaultConfig = {
19
+ defaultJql: "assignee = currentUser() AND status != Done ORDER BY updated DESC",
20
+ refreshInterval: 30,
21
+ projectMap: {},
22
+ workspaceId: null,
23
+ defaultProjectId: null,
24
+ defaultProjectName: null,
25
+ defaultBillable: true
26
+ };
27
+ export class ConfigService extends Context.Tag("jcf/ConfigService")() {
28
+ }
29
+ const CONFIG_DIR = ".jcf";
30
+ const CONFIG_FILE = "config.json";
31
+ export const layer = Layer.effect(ConfigService, Effect.gen(function* () {
32
+ const fs = yield* FileSystem.FileSystem;
33
+ const path = yield* Path.Path;
34
+ const home = yield* Effect.sync(() => homedir());
35
+ const dir = path.join(home, CONFIG_DIR);
36
+ const filePath = path.join(dir, CONFIG_FILE);
37
+ const ensureDir = Effect.gen(function* () {
38
+ const exists = yield* fs.exists(dir);
39
+ if (!exists)
40
+ yield* fs.makeDirectory(dir, { recursive: true });
41
+ });
42
+ const read = Effect.gen(function* () {
43
+ const exists = yield* fs.exists(filePath);
44
+ if (!exists)
45
+ return defaultConfig;
46
+ const content = yield* fs.readFileString(filePath);
47
+ const parsed = yield* Effect.try({
48
+ try: () => JSON.parse(content),
49
+ catch: () => ({})
50
+ });
51
+ return { ...defaultConfig, ...parsed };
52
+ }).pipe(Effect.catchAll(() => Effect.succeed(defaultConfig)));
53
+ const write = (config) => Effect.gen(function* () {
54
+ yield* ensureDir;
55
+ yield* fs.writeFileString(filePath, JSON.stringify(config, null, 2));
56
+ });
57
+ return {
58
+ get: read,
59
+ set: (patch) => Effect.gen(function* () {
60
+ const current = yield* read;
61
+ yield* write({ ...current, ...patch });
62
+ }).pipe(Effect.catchAll(() => Effect.void)),
63
+ configDir: Effect.succeed(dir)
64
+ };
65
+ }));
66
+ //# sourceMappingURL=ConfigService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigService.js","sourceRoot":"","sources":["../../../src/services/ConfigService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Atomic timer state persistence to `~/.jcf/state.json`.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Atomic writes**: Write goes to a `.tmp` file then `rename` — prevents corruption if
7
+ * the process crashes mid-write.
8
+ * - **External consumption**: The state file is read by the Neovim plugin and statusline
9
+ * integrations to show timer state outside the TUI.
10
+ *
11
+ * @module
12
+ */
13
+ import * as FileSystem from "@effect/platform/FileSystem";
14
+ import * as Path from "@effect/platform/Path";
15
+ import * as Context from "effect/Context";
16
+ import * as Effect from "effect/Effect";
17
+ import * as Layer from "effect/Layer";
18
+ import { homedir } from "node:os";
19
+ const emptyState = {
20
+ active: false,
21
+ ticketKey: null,
22
+ summary: null,
23
+ project: null,
24
+ startedAt: null,
25
+ startedAt_unix: null,
26
+ elapsed: 0,
27
+ clockifyEntryId: null
28
+ };
29
+ export class StateWriter extends Context.Tag("jcf/StateWriter")() {
30
+ }
31
+ const STATE_DIR = ".jcf";
32
+ const STATE_FILE = "state.json";
33
+ export const layer = Layer.effect(StateWriter, Effect.gen(function* () {
34
+ const fs = yield* FileSystem.FileSystem;
35
+ const path = yield* Path.Path;
36
+ const home = yield* Effect.sync(() => homedir());
37
+ const dir = path.join(home, STATE_DIR);
38
+ const filePath = path.join(dir, STATE_FILE);
39
+ const ensureDir = Effect.gen(function* () {
40
+ const exists = yield* fs.exists(dir);
41
+ if (!exists)
42
+ yield* fs.makeDirectory(dir, { recursive: true });
43
+ });
44
+ return {
45
+ write: (state) => Effect.gen(function* () {
46
+ yield* ensureDir;
47
+ const tmpPath = `${filePath}.tmp`;
48
+ yield* fs.writeFileString(tmpPath, JSON.stringify(state, null, 2));
49
+ yield* fs.rename(tmpPath, filePath);
50
+ }).pipe(Effect.catchAll(() => Effect.void)),
51
+ read: Effect.gen(function* () {
52
+ const exists = yield* fs.exists(filePath);
53
+ if (!exists)
54
+ return emptyState;
55
+ const content = yield* fs.readFileString(filePath);
56
+ return yield* Effect.try({
57
+ try: () => JSON.parse(content),
58
+ catch: () => emptyState
59
+ });
60
+ }).pipe(Effect.catchAll(() => Effect.succeed(emptyState))),
61
+ clear: Effect.gen(function* () {
62
+ yield* ensureDir;
63
+ const tmpPath = `${filePath}.tmp`;
64
+ yield* fs.writeFileString(tmpPath, JSON.stringify(emptyState, null, 2));
65
+ yield* fs.rename(tmpPath, filePath);
66
+ }).pipe(Effect.catchAll(() => Effect.void))
67
+ };
68
+ }));
69
+ //# sourceMappingURL=StateWriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StateWriter.js","sourceRoot":"","sources":["../../../src/services/StateWriter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Jira ticket fetching with reactive state via SubscriptionRef.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **SubscriptionRef-backed state**: {@link TicketServiceShape.state} is a `SubscriptionRef<TicketState>`
7
+ * that the TUI subscribes to for live updates. {@link TicketServiceShape.refresh} fetches
8
+ * tickets from Jira and updates the ref.
9
+ * - **Field extraction helpers**: `extractNested` and `extractString` safely navigate the
10
+ * loosely-typed Jira API response without runtime crashes.
11
+ * - **In-memory search**: {@link TicketServiceShape.search} filters the cached ticket list
12
+ * by key or summary substring.
13
+ *
14
+ * @module
15
+ */
16
+ import { JiraApiClient, toEffect } from "@knpkv/jira-api-client";
17
+ import * as Context from "effect/Context";
18
+ import * as Data from "effect/Data";
19
+ import * as Effect from "effect/Effect";
20
+ import * as Layer from "effect/Layer";
21
+ import * as SubscriptionRef from "effect/SubscriptionRef";
22
+ import { ConfigService } from "./ConfigService.js";
23
+ export class TicketError extends Data.TaggedError("TicketError") {
24
+ }
25
+ const emptyState = {
26
+ tickets: [],
27
+ loading: false,
28
+ error: null,
29
+ lastRefreshed: null
30
+ };
31
+ // ---------------------------------------------------------------------------
32
+ // Helpers
33
+ // ---------------------------------------------------------------------------
34
+ const extractField = (fields, key) => fields?.[key] ?? null;
35
+ const extractString = (fields, key) => {
36
+ const val = extractField(fields, key);
37
+ return typeof val === "string" ? val : null;
38
+ };
39
+ const extractNested = (fields, key, nested) => {
40
+ const val = extractField(fields, key);
41
+ if (val && typeof val === "object" && nested in val) {
42
+ const v = val[nested];
43
+ return typeof v === "string" ? v : null;
44
+ }
45
+ return null;
46
+ };
47
+ export class TicketService extends Context.Tag("jcf/TicketService")() {
48
+ }
49
+ export const layer = Layer.effect(TicketService, Effect.gen(function* () {
50
+ const config = yield* ConfigService;
51
+ const jira = yield* JiraApiClient;
52
+ const ref = yield* SubscriptionRef.make(emptyState);
53
+ const refresh = Effect.gen(function* () {
54
+ yield* SubscriptionRef.set(ref, { ...emptyState, loading: true });
55
+ const cfg = yield* config.get;
56
+ const jql = cfg.defaultJql;
57
+ const result = yield* toEffect(jira.v3.client.GET("/rest/api/3/search/jql", {
58
+ params: {
59
+ query: {
60
+ jql,
61
+ maxResults: 50,
62
+ fields: ["summary", "status", "priority", "assignee", "issuetype", "labels", "updated"]
63
+ }
64
+ }
65
+ })).pipe(Effect.mapError((e) => new TicketError({ message: `Jira search failed: ${String(e)}`, cause: e })));
66
+ const tickets = (result.issues ?? []).map((issue) => {
67
+ const fields = issue.fields;
68
+ return {
69
+ key: String(issue.key ?? "?"),
70
+ summary: extractString(fields, "summary") ?? "(no summary)",
71
+ status: extractNested(fields, "status", "name") ?? "Unknown",
72
+ priority: extractNested(fields, "priority", "name"),
73
+ assignee: extractNested(fields, "assignee", "displayName"),
74
+ type: extractNested(fields, "issuetype", "name") ?? "Task",
75
+ labels: Array.isArray(fields?.["labels"]) ? fields["labels"] : [],
76
+ updated: extractString(fields, "updated") ?? new Date().toISOString()
77
+ };
78
+ });
79
+ yield* SubscriptionRef.set(ref, {
80
+ tickets,
81
+ loading: false,
82
+ error: null,
83
+ lastRefreshed: new Date()
84
+ });
85
+ }).pipe(Effect.catchAll((e) => {
86
+ const msg = e._tag === "TicketError"
87
+ ? e.message
88
+ : `Failed to fetch tickets: ${String(e)}`;
89
+ return Effect.logDebug(`TicketService refresh error: ${msg}`).pipe(Effect.flatMap(() => SubscriptionRef.set(ref, {
90
+ tickets: [],
91
+ loading: false,
92
+ error: msg,
93
+ lastRefreshed: null
94
+ })));
95
+ }));
96
+ const search = (text) => Effect.gen(function* () {
97
+ const state = yield* SubscriptionRef.get(ref);
98
+ if (!text.trim())
99
+ return state.tickets;
100
+ const lower = text.toLowerCase();
101
+ return state.tickets.filter((t) => t.key.toLowerCase().includes(lower) ||
102
+ t.summary.toLowerCase().includes(lower));
103
+ });
104
+ return { state: ref, refresh, search };
105
+ }));
106
+ //# sourceMappingURL=TicketService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TicketService.js","sourceRoot":"","sources":["../../../src/services/TicketService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Core timer lifecycle — start, stop, detect running — bridging Clockify and Jira worklog.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Dual write**: Starting a timer creates a Clockify time entry AND updates the local
7
+ * state file (for Neovim/statusline). Stopping updates the Clockify entry AND posts
8
+ * a Jira worklog via raw HTTP (generated client swallows 4xx as void).
9
+ * - **Auto-resolution**: Project ID, billable flag, and tags are resolved from config defaults,
10
+ * Clockify project name matching, and Jira issue type/labels.
11
+ * - **External detection**: {@link TimerServiceShape.detectRunning} polls Clockify for a
12
+ * running timer not started by jcf and syncs local state.
13
+ *
14
+ * **Gotchas**
15
+ *
16
+ * - Jira worklog uses raw HTTP because the generated client returns void for 4xx — check
17
+ * `response.status` manually.
18
+ * - `timeSpentSeconds` is floored to 60s minimum (Jira rejects <60s worklogs).
19
+ *
20
+ * @module
21
+ */
22
+ import * as HttpClient from "@effect/platform/HttpClient";
23
+ import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
24
+ import { ClockifyApiClient } from "@knpkv/clockify-api-client";
25
+ import { JiraApiClient } from "@knpkv/jira-api-client";
26
+ import { JiraAuth } from "@knpkv/jira-cli/JiraAuth";
27
+ import * as Context from "effect/Context";
28
+ import * as Data from "effect/Data";
29
+ import * as Duration from "effect/Duration";
30
+ import * as Effect from "effect/Effect";
31
+ import * as Layer from "effect/Layer";
32
+ import * as Redacted from "effect/Redacted";
33
+ import * as SubscriptionRef from "effect/SubscriptionRef";
34
+ import { ClockifyAuth } from "./ClockifyAuth.js";
35
+ import { ConfigService } from "./ConfigService.js";
36
+ import { StateWriter } from "./StateWriter.js";
37
+ export class TimerError extends Data.TaggedError("TimerError") {
38
+ }
39
+ const emptyState = {
40
+ active: false,
41
+ ticketKey: null,
42
+ summary: null,
43
+ project: null,
44
+ startedAt: null,
45
+ clockifyEntryId: null,
46
+ projectId: null,
47
+ projectName: null,
48
+ billable: null,
49
+ startedViaJcf: false
50
+ };
51
+ export class TimerService extends Context.Tag("jcf/TimerService")() {
52
+ }
53
+ export const layer = Layer.effect(TimerService, Effect.gen(function* () {
54
+ const clockify = yield* ClockifyApiClient;
55
+ yield* JiraApiClient; // ensure dep is in layer
56
+ const httpClient = yield* HttpClient.HttpClient;
57
+ const jiraAuth = yield* JiraAuth;
58
+ const clockifyAuth = yield* ClockifyAuth;
59
+ const config = yield* ConfigService;
60
+ const stateWriter = yield* StateWriter;
61
+ const ref = yield* SubscriptionRef.make(emptyState);
62
+ const tagCache = new Map();
63
+ const writeStateFile = (state) => {
64
+ const file = {
65
+ active: state.active,
66
+ ticketKey: state.ticketKey,
67
+ summary: state.summary,
68
+ project: state.project,
69
+ startedAt: state.startedAt?.toISOString() ?? null,
70
+ startedAt_unix: state.startedAt ? Math.floor(state.startedAt.getTime() / 1000) : null,
71
+ elapsed: state.startedAt ? Math.floor((Date.now() - state.startedAt.getTime()) / 1000) : 0,
72
+ clockifyEntryId: state.clockifyEntryId
73
+ };
74
+ return stateWriter.write(file);
75
+ };
76
+ const getAuth = clockifyAuth.getConfig.pipe(Effect.mapError((e) => new TimerError({ message: e.message })));
77
+ const start = (ticket, options) => Effect.gen(function* () {
78
+ const auth = yield* getAuth;
79
+ const cfg = yield* config.get;
80
+ // Auto-stop existing timer
81
+ const current = yield* SubscriptionRef.get(ref);
82
+ if (current.active) {
83
+ yield* internalStop().pipe(Effect.catchAll((e) => Effect.logWarning(`Auto-stop failed, Clockify entry may be orphaned: ${e.message}`)));
84
+ }
85
+ // Resolve projectId: explicit > config default > auto-match by name > null
86
+ let projectId = options?.projectId ?? cfg.defaultProjectId ?? null;
87
+ if (!projectId) {
88
+ const jiraProject = ticket.key.split("-")[0] ?? "";
89
+ const clockifyProjectName = cfg.projectMap[jiraProject] ?? jiraProject;
90
+ const project = yield* clockify.getProjectByName(auth.workspaceId, clockifyProjectName).pipe(Effect.catchAll(() => Effect.succeed(null)));
91
+ if (project)
92
+ projectId = project.id;
93
+ }
94
+ // Resolve project name
95
+ let projectName = cfg.defaultProjectName ?? null;
96
+ if (projectId && !projectName) {
97
+ const projects = yield* clockify.getProjects(auth.workspaceId).pipe(Effect.catchAll(() => Effect.succeed([])));
98
+ projectName = projects.find((p) => p.id === projectId)?.name ?? null;
99
+ }
100
+ // Resolve billable: explicit > config default > null (will prompt on stop)
101
+ const billable = options?.billable ?? cfg.defaultBillable ?? null;
102
+ // Resolve tags: ticket type + Jira labels → Clockify tags
103
+ const tagNames = [ticket.type, ...ticket.labels].filter(Boolean);
104
+ const tagIds = [];
105
+ for (const tagName of tagNames) {
106
+ const cached = tagCache.get(tagName);
107
+ if (cached) {
108
+ tagIds.push(cached);
109
+ continue;
110
+ }
111
+ const tag = yield* clockify.findOrCreateTag(auth.workspaceId, tagName).pipe(Effect.catchAll(() => Effect.succeed(null)));
112
+ if (tag) {
113
+ tagIds.push(tag.id);
114
+ tagCache.set(tagName, tag.id);
115
+ }
116
+ }
117
+ yield* Effect.logDebug(`Clockify tags: ${tagNames.join(", ")} → ${tagIds.length} tag IDs`);
118
+ // Start timer
119
+ const now = new Date();
120
+ const entry = yield* clockify.createTimeEntry(auth.workspaceId, {
121
+ description: `[${ticket.key}] ${ticket.summary}`,
122
+ start: now.toISOString(),
123
+ ...(projectId ? { projectId } : {}),
124
+ ...(billable !== null ? { billable } : {}),
125
+ ...(tagIds.length > 0 ? { tagIds } : {})
126
+ }).pipe(Effect.mapError((e) => new TimerError({ message: `Failed to start timer: ${e.message}`, cause: e })));
127
+ const newState = {
128
+ active: true,
129
+ ticketKey: ticket.key,
130
+ summary: ticket.summary,
131
+ project: ticket.key.split("-")[0] ?? null,
132
+ startedAt: now,
133
+ clockifyEntryId: entry.id,
134
+ projectId,
135
+ projectName,
136
+ billable,
137
+ startedViaJcf: true
138
+ };
139
+ yield* writeStateFile(newState);
140
+ yield* SubscriptionRef.set(ref, newState);
141
+ });
142
+ const internalStop = (options) => Effect.gen(function* () {
143
+ const auth = yield* getAuth;
144
+ const current = yield* SubscriptionRef.get(ref);
145
+ if (!current.active || !current.startedAt) {
146
+ return yield* Effect.fail(new TimerError({ message: "No active timer" }));
147
+ }
148
+ const now = new Date();
149
+ const durationMs = now.getTime() - current.startedAt.getTime();
150
+ const duration = Duration.millis(durationMs);
151
+ // Merge: stop options override current state
152
+ const projectId = options?.projectId ?? current.projectId ?? null;
153
+ const billable = options?.billable ?? current.billable ?? null;
154
+ const comment = options?.comment;
155
+ // Stop via PUT — preserve existing tagIds from the entry
156
+ if (current.clockifyEntryId) {
157
+ const existing = yield* clockify.getTimeEntry(auth.workspaceId, current.clockifyEntryId).pipe(Effect.catchAll(() => Effect.succeed(null)));
158
+ const tagIds = existing?.tagIds ?? [];
159
+ // Append comment to Clockify description if provided
160
+ const description = comment
161
+ ? `${existing?.description ?? ""} | ${comment}`
162
+ : undefined;
163
+ yield* clockify.updateTimeEntry(auth.workspaceId, current.clockifyEntryId, {
164
+ start: current.startedAt.toISOString(),
165
+ end: now.toISOString(),
166
+ ...(description !== undefined ? { description } : {}),
167
+ ...(projectId ? { projectId } : {}),
168
+ ...(billable !== null ? { billable } : {}),
169
+ ...(tagIds.length > 0 ? { tagIds: [...tagIds] } : {})
170
+ }).pipe(Effect.mapError((e) => new TimerError({ message: `Failed to stop timer: ${e.message}`, cause: e })));
171
+ }
172
+ else {
173
+ yield* clockify.stopTimer(auth.workspaceId, auth.userId, {
174
+ end: now.toISOString()
175
+ }).pipe(Effect.mapError((e) => new TimerError({ message: `Failed to stop timer: ${e.message}`, cause: e })));
176
+ }
177
+ // Log Jira worklog (raw HTTP — generated client swallows 4xx as void)
178
+ let jiraLogged = false;
179
+ if (current.ticketKey) {
180
+ // Jira rejects worklogs <60s — floor to 60s. Clockify keeps actual elapsed.
181
+ const timeSpent = Math.max(60, Math.floor(durationMs / 1000));
182
+ const started = current.startedAt.toISOString().replace("Z", "+0000");
183
+ yield* Effect.logDebug(`Jira worklog: ${current.ticketKey} ${timeSpent}s`);
184
+ const accessToken = yield* jiraAuth.getAccessToken().pipe(Effect.tapError((e) => Effect.logDebug(`Jira getAccessToken failed: ${String(e)}`)), Effect.catchAll(() => Effect.succeed(Redacted.make(""))));
185
+ const cloudId = yield* jiraAuth.getCloudId().pipe(Effect.catchAll(() => Effect.succeed("")));
186
+ const response = yield* httpClient.execute(HttpClientRequest.post(`https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/issue/${current.ticketKey}/worklog`).pipe(HttpClientRequest.setHeader("Authorization", `Bearer ${Redacted.value(accessToken)}`), HttpClientRequest.setHeader("Content-Type", "application/json"), HttpClientRequest.bodyUnsafeJson({
187
+ started,
188
+ timeSpentSeconds: timeSpent,
189
+ ...(comment ?
190
+ {
191
+ comment: {
192
+ type: "doc",
193
+ version: 1,
194
+ content: [{ type: "paragraph", content: [{ type: "text", text: comment }] }]
195
+ }
196
+ } :
197
+ {})
198
+ }))).pipe(Effect.catchAll((e) => Effect.logDebug(`Jira worklog failed: ${String(e)}`).pipe(Effect.map(() => null))));
199
+ if (response && response.status >= 200 && response.status < 300) {
200
+ jiraLogged = true;
201
+ yield* Effect.logDebug(`Jira worklog created (${response.status})`);
202
+ }
203
+ else if (response) {
204
+ const body = yield* response.text.pipe(Effect.catchAll(() => Effect.succeed("")));
205
+ yield* Effect.logDebug(`Jira worklog failed (${response.status}): ${body.slice(0, 300)}`);
206
+ }
207
+ }
208
+ yield* SubscriptionRef.set(ref, emptyState);
209
+ yield* stateWriter.clear;
210
+ return {
211
+ duration,
212
+ clockifyLogged: true,
213
+ jiraWorklogLogged: jiraLogged,
214
+ needsProjectId: !projectId,
215
+ needsBillable: billable === null
216
+ };
217
+ });
218
+ const detectRunning = Effect.gen(function* () {
219
+ const auth = yield* getAuth;
220
+ const running = yield* clockify.getRunningTimer(auth.workspaceId, auth.userId).pipe(Effect.catchAll(() => Effect.succeed(null)));
221
+ if (running && running.timeInterval.start) {
222
+ const startedAt = new Date(running.timeInterval.start);
223
+ // Parse "[KEY] summary" or "KEY: summary" format
224
+ const desc = running.description ?? "";
225
+ const bracketMatch = desc.match(/^\[([^\]]+)\]\s*(.*)$/);
226
+ const colonMatch = desc.match(/^([^:]+):\s*(.*)$/);
227
+ const ticketKey = bracketMatch?.[1]?.trim() ?? colonMatch?.[1]?.trim() ?? null;
228
+ const summary = bracketMatch?.[2]?.trim() ?? colonMatch?.[2]?.trim() ?? null;
229
+ if (!ticketKey) {
230
+ yield* Effect.logWarning(`Running Clockify timer has unparseable description: "${desc}"`);
231
+ }
232
+ // Resolve project name
233
+ let resolvedProjectName = null;
234
+ if (running.projectId) {
235
+ const projects = yield* clockify.getProjects(auth.workspaceId).pipe(Effect.catchAll(() => Effect.succeed([])));
236
+ resolvedProjectName = projects.find((p) => p.id === running.projectId)?.name ?? null;
237
+ }
238
+ const newState = {
239
+ active: true,
240
+ ticketKey,
241
+ summary,
242
+ project: ticketKey?.split("-")[0] ?? null,
243
+ startedAt,
244
+ clockifyEntryId: running.id,
245
+ projectId: running.projectId ?? null,
246
+ projectName: resolvedProjectName,
247
+ billable: running.billable ?? null,
248
+ startedViaJcf: false
249
+ };
250
+ yield* writeStateFile(newState);
251
+ yield* SubscriptionRef.set(ref, newState);
252
+ }
253
+ });
254
+ // Discard: delete the Clockify entry, clear state, no Jira worklog
255
+ const discard = Effect.gen(function* () {
256
+ const current = yield* SubscriptionRef.get(ref);
257
+ if (!current.active) {
258
+ return yield* Effect.fail(new TimerError({ message: "No active timer to discard" }));
259
+ }
260
+ const auth = yield* getAuth;
261
+ if (current.clockifyEntryId) {
262
+ yield* clockify
263
+ .deleteTimeEntry(auth.workspaceId, current.clockifyEntryId)
264
+ .pipe(Effect.mapError(() => new TimerError({ message: "delete failed" })), Effect.catchTag("TimerError", () => Effect.void));
265
+ }
266
+ yield* SubscriptionRef.set(ref, emptyState);
267
+ yield* stateWriter.clear;
268
+ });
269
+ return { state: ref, start, stop: internalStop, discard, detectRunning };
270
+ }));
271
+ //# sourceMappingURL=TimerService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimerService.js","sourceRoot":"","sources":["../../../src/services/TimerService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,UAAU,MAAM,6BAA6B,CAAA;AACzD,OAAO,KAAK,iBAAiB,MAAM,oCAAoC,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAuB,MAAM,kBAAkB,CAAA;AA8BnE,MAAM,OAAO,UAAW,SAAQ,IAAI,CAAC,WAAW,CAAC,YAAY,CAG3D;CAAG;AAEL,MAAM,UAAU,GAAe;IAC7B,MAAM,EAAE,KAAK;IACb,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,IAAI;IACf,eAAe,EAAE,IAAI;IACrB,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,IAAI;IACjB,QAAQ,EAAE,IAAI;IACd,aAAa,EAAE,KAAK;CACrB,CAAA;AAyBD,MAAM,OAAO,YAAa,SAAQ,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAmC;CAAG;AAEvG,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAC/B,YAAY,EACZ,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAA;IACzC,KAAK,CAAC,CAAC,aAAa,CAAA,CAAC,yBAAyB;IAC9C,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAA;IAC/C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAA;IAChC,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,YAAY,CAAA;IACxC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,aAAa,CAAA;IACnC,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,WAAW,CAAA;IACtC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI,CAAa,UAAU,CAAC,CAAA;IAC/D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;IAE1C,MAAM,cAAc,GAAG,CAAC,KAAiB,EAAE,EAAE;QAC3C,MAAM,IAAI,GAAmB;YAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,IAAI;YACjD,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;YACrF,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1F,eAAe,EAAE,KAAK,CAAC,eAAe;SACvC,CAAA;QACD,OAAO,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAChC,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,CAAC,IAAI,CACzC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAC/D,CAAA;IAED,MAAM,KAAK,GAAG,CAAC,MAAkB,EAAE,OAAsB,EAAE,EAAE,CAC3D,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,CAAA;QAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA;QAE7B,2BAA2B;QAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC/C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,KAAK,CAAC,CAAC,YAAY,EAAE,CAAC,IAAI,CACxB,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,qDAAqD,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAC5G,CAAA;QACH,CAAC;QAED,2EAA2E;QAC3E,IAAI,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAA;QAClE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YAClD,MAAM,mBAAmB,GAAG,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,WAAW,CAAA;YACtE,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC,IAAI,CAC1F,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAC5C,CAAA;YACD,IAAI,OAAO;gBAAE,SAAS,GAAG,OAAO,CAAC,EAAE,CAAA;QACrC,CAAC;QAED,uBAAuB;QACvB,IAAI,WAAW,GAAkB,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAA;QAC/D,IAAI,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CACjE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAW,CAAC,CAAC,CACnD,CAAA;YACD,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,IAAI,IAAI,IAAI,CAAA;QACtE,CAAC;QAED,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,GAAG,CAAC,eAAe,IAAI,IAAI,CAAA;QAEjE,0DAA0D;QAC1D,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAChE,MAAM,MAAM,GAAkB,EAAE,CAAA;QAChC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACpC,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;gBACnB,SAAQ;YACV,CAAC;YACD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CACzE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAC5C,CAAA;YACD,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;gBACnB,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;YAC/B,CAAC;QACH,CAAC;QACD,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,MAAM,UAAU,CAAC,CAAA;QAE1F,cAAc;QACd,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;QACtB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE;YAC9D,WAAW,EAAE,IAAI,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,OAAO,EAAE;YAChD,KAAK,EAAE,GAAG,CAAC,WAAW,EAAE;YACxB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnC,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACzC,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,0BAA0B,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CACrG,CAAA;QAED,MAAM,QAAQ,GAAe;YAC3B,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,MAAM,CAAC,GAAG;YACrB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI;YACzC,SAAS,EAAE,GAAG;YACd,eAAe,EAAE,KAAK,CAAC,EAAE;YACzB,SAAS;YACT,WAAW;YACX,QAAQ;YACR,aAAa,EAAE,IAAI;SACpB,CAAA;QAED,KAAK,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;QAC/B,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEJ,MAAM,YAAY,GAAG,CAAC,OAAqB,EAAE,EAAE,CAC7C,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,CAAA;QAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAE/C,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;YAC1C,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAA;QAC3E,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;QACtB,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,CAAA;QAC9D,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;QAE5C,6CAA6C;QAC7C,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,OAAO,CAAC,SAAS,IAAI,IAAI,CAAA;QACjE,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAA;QAC9D,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,CAAA;QAEhC,yDAAyD;QACzD,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,IAAI,CAC3F,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAC5C,CAAA;YACD,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,IAAI,EAAE,CAAA;YAErC,qDAAqD;YACrD,MAAM,WAAW,GAAG,OAAO;gBACzB,CAAC,CAAC,GAAG,QAAQ,EAAE,WAAW,IAAI,EAAE,MAAM,OAAO,EAAE;gBAC/C,CAAC,CAAC,SAAS,CAAA;YAEb,KAAK,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,eAAe,EAAE;gBACzE,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE;gBACtC,GAAG,EAAE,GAAG,CAAC,WAAW,EAAE;gBACtB,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrD,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnC,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1C,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtD,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,yBAAyB,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CACpG,CAAA;QACH,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE;gBACvD,GAAG,EAAE,GAAG,CAAC,WAAW,EAAE;aACvB,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,yBAAyB,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CACpG,CAAA;QACH,CAAC;QAED,sEAAsE;QACtE,IAAI,UAAU,GAAG,KAAK,CAAA;QACtB,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,4EAA4E;YAC5E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAA;YAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;YACrE,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,OAAO,CAAC,SAAS,IAAI,SAAS,GAAG,CAAC,CAAA;YAE1E,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,IAAI,CACvD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,+BAA+B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACnF,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CACzD,CAAA;YACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;YAE5F,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,OAAO,CACxC,iBAAiB,CAAC,IAAI,CACpB,qCAAqC,OAAO,qBAAqB,OAAO,CAAC,SAAS,UAAU,CAC7F,CAAC,IAAI,CACJ,iBAAiB,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,EACrF,iBAAiB,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,EAC/D,iBAAiB,CAAC,cAAc,CAAC;gBAC/B,OAAO;gBACP,gBAAgB,EAAE,SAAS;gBAC3B,GAAG,CAAC,OAAO,CAAC,CAAC;oBACX;wBACE,OAAO,EAAE;4BACP,IAAI,EAAE,KAAK;4BACX,OAAO,EAAE,CAAC;4BACV,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;yBAC7E;qBACF,CAAC,CAAC;oBACH,EAAE,CAAC;aACN,CAAC,CACH,CACF,CAAC,IAAI,CACJ,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAC1G,CAAA;YAED,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBAChE,UAAU,GAAG,IAAI,CAAA;gBACjB,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;YACrE,CAAC;iBAAM,IAAI,QAAQ,EAAE,CAAC;gBACpB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;gBACjF,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,QAAQ,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;YAC3F,CAAC;QACH,CAAC;QAED,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAA;QAC3C,KAAK,CAAC,CAAC,WAAW,CAAC,KAAK,CAAA;QAExB,OAAO;YACL,QAAQ;YACR,cAAc,EAAE,IAAI;YACpB,iBAAiB,EAAE,UAAU;YAC7B,cAAc,EAAE,CAAC,SAAS;YAC1B,aAAa,EAAE,QAAQ,KAAK,IAAI;SACZ,CAAA;IACxB,CAAC,CAAC,CAAA;IAEJ,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,CAAA;QAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CACjF,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAC5C,CAAA;QAED,IAAI,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;YAC1C,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;YACtD,iDAAiD;YACjD,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAA;YACtC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAA;YACxD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAA;YAClD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAA;YAC9E,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAA;YAE5E,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,wDAAwD,IAAI,GAAG,CAChE,CAAA;YACH,CAAC;YAED,uBAAuB;YACvB,IAAI,mBAAmB,GAAkB,IAAI,CAAA;YAC7C,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CACjE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAW,CAAC,CAAC,CACnD,CAAA;gBACD,mBAAmB,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,IAAI,IAAI,CAAA;YACtF,CAAC;YAED,MAAM,QAAQ,GAAe;gBAC3B,MAAM,EAAE,IAAI;gBACZ,SAAS;gBACT,OAAO;gBACP,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI;gBACzC,SAAS;gBACT,eAAe,EAAE,OAAO,CAAC,EAAE;gBAC3B,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;gBACpC,WAAW,EAAE,mBAAmB;gBAChC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;gBAClC,aAAa,EAAE,KAAK;aACrB,CAAA;YAED,KAAK,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;YAC/B,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,mEAAmE;IACnE,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC/C,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,4BAA4B,EAAE,CAAC,CAAC,CAAA;QACtF,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,CAAA;QAC3B,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,KAAK,CAAC,CAAC,QAAQ;iBACZ,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC;iBAC1D,IAAI,CACH,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC,EACnE,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CACjD,CAAA;QACL,CAAC;QACD,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAA;QAC3C,KAAK,CAAC,CAAC,WAAW,CAAC,KAAK,CAAA;IAC1B,CAAC,CAAC,CAAA;IAEF,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;AAC1E,CAAC,CAAC,CACH,CAAA"}