@2digits/tlo-mcp 0.1.38 → 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.
- package/README.md +79 -71
- package/dist/bin.mjs +18 -1
- package/package.json +12 -12
- package/dist/TloLive-DHG59kAi.mjs +0 -1
- package/dist/bin.d.mts +0 -1
- package/dist/index.d.mts +0 -513
- package/dist/index.mjs +0 -1
package/README.md
CHANGED
|
@@ -1,103 +1,111 @@
|
|
|
1
1
|
# @2digits/tlo-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One personal Teamleader Orbit gateway: local OAuth → authenticated HTTP MCP → OpenTunnel → Executor Cloud.
|
|
4
|
+
Only Orbit tools are exposed. No local Executor, cookie adapter, Amp plugin, Vercel deployment or hosted OAuth bridge.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
+
## Run
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Requires Node.js 24+. In this repository, the root `pitchfork.toml` runs the gateway in the background.
|
|
9
|
+
With `mise activate` enabled in your shell, entering the repository installs the pinned Pitchfork version and
|
|
10
|
+
starts the gateway automatically. It keeps running after you leave the repository or close the terminal.
|
|
11
|
+
The Mac must stay awake. Pitchfork retries failed processes up to three times and waits for the public tunnel
|
|
12
|
+
to be ready, not just the local HTTP port. Each start builds the CLI; source edits need a restart.
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
### As MCP Server
|
|
14
|
+
From the repository root:
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
|
-
|
|
17
|
+
mise exec -- pitchfork status tlo-mcp
|
|
18
|
+
mise exec -- pitchfork logs tlo-mcp --tail
|
|
19
|
+
mise exec -- pitchfork restart tlo-mcp
|
|
20
|
+
mise exec -- pitchfork stop tlo-mcp
|
|
17
21
|
```
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
The public endpoint and Executor connection prompt are in the logs; the gateway key is never printed.
|
|
24
|
+
Stopping the log viewer does not stop the gateway. Stop any manually started gateway before enabling Pitchfork,
|
|
25
|
+
since both use port `4790`. First-time Orbit login still requires completing the browser flow.
|
|
26
|
+
To keep the gateway stopped across repository visits, use `mise exec -- pitchfork disable tlo-mcp`;
|
|
27
|
+
resume with `mise exec -- pitchfork enable tlo-mcp` followed by `mise exec -- pitchfork start tlo-mcp`.
|
|
28
|
+
This setup starts on repository entry, not automatically at macOS login.
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
To run in the foreground instead, first stop/disable the Pitchfork daemon. From this package:
|
|
24
31
|
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
└── PROJECT (e.g., "I amsterdam | doorontwikkeling")
|
|
28
|
-
├── TASK (work items with budget/workload)
|
|
29
|
-
│ └── ACTIVITY (time entries)
|
|
30
|
-
└── BOARD (kanban)
|
|
31
|
-
└── BOARDLIST (column)
|
|
32
|
-
└── TODO (card)
|
|
32
|
+
```bash
|
|
33
|
+
vp run run
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
Or, after installation/build:
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- `todoId` - Kanban card
|
|
41
|
-
- `contactId` - User identifier
|
|
38
|
+
```bash
|
|
39
|
+
vp exec tlo-mcp
|
|
40
|
+
```
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
The command:
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
1. Creates/reuses a private gateway API key.
|
|
45
|
+
2. Opens Orbit login if no local OAuth session exists (fresh client registration, state and PKCE).
|
|
46
|
+
3. Discovers the official Orbit tools and starts `/mcp` on `127.0.0.1:4790`.
|
|
47
|
+
4. Connects an isolated `tlo-mcp` OpenTunnel profile with only the `tlo` route.
|
|
48
|
+
5. Prints a public HTTPS endpoint and a ready-to-paste agent prompt for Executor Cloud.
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| `get_tasks` | Get tasks within a project |
|
|
52
|
-
| `get_tasks_for_user` | Get tasks assigned to a user |
|
|
53
|
-
| `set_task_state` | Change task state (DRAFT/OPEN/COMPLETED/CLOSED) |
|
|
50
|
+
OpenTunnel runs inside this process using its Effect SDK. No separate tunnel CLI or daemon is installed.
|
|
51
|
+
Keep this process running and the computer awake. Ctrl-C closes the HTTP server and tunnel; identity and key persist
|
|
52
|
+
so subsequent starts reuse the URL. Use `--port 4791` if the default port is occupied.
|
|
53
|
+
The first tunnel certificate can take several minutes. Failed startup closes acquired resources.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
## Connect Executor Cloud
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
| `get_week_activities` | Get all time entries for a week |
|
|
60
|
-
| `create_activity` | Log time against project/task |
|
|
61
|
-
| `update_activity` | Modify existing time entry |
|
|
62
|
-
| `delete_activity` | Remove time entry |
|
|
57
|
+
Paste the printed prompt into an agent with Executor tools. It registers a Streamable HTTP MCP integration with
|
|
58
|
+
API-key auth: `Authorization: Bearer <gateway-key>`. **Do not configure Orbit OAuth in Executor.**
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
The key is stored at `~/.config/2digits/tlo-mcp/gateway.key` (mode `0600`, directory `0700`).
|
|
61
|
+
On macOS, copy it without printing it:
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
| `move_todo` | Move card between columns |
|
|
63
|
+
```bash
|
|
64
|
+
pbcopy < ~/.config/2digits/tlo-mcp/gateway.key
|
|
65
|
+
```
|
|
70
66
|
|
|
71
|
-
|
|
67
|
+
Enter it directly in Executor's secure credential form, never in agent chat. On other platforms, open the private
|
|
68
|
+
file locally and copy it into that form. The agent prompt contains no credentials. Executor must use its secure
|
|
69
|
+
handoff for the API key and verify one read-only tool after connecting.
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
| -------------- | ---------------------------------- |
|
|
75
|
-
| `get_messages` | Get comments on project/board/todo |
|
|
76
|
-
| `post_message` | Add comment to project/board/todo |
|
|
71
|
+
## Authentication and security
|
|
77
72
|
|
|
78
|
-
|
|
73
|
+
- Orbit credentials remain in macOS Keychain (`@2digits/tlo-mcp/orbit`). Other platforms use a private local session file.
|
|
74
|
+
- OAuth access tokens refresh before expiry; rotated refresh tokens are persisted before use.
|
|
75
|
+
- The gateway key is separate from Orbit credentials. All HTTP routes/methods require it before MCP parsing.
|
|
76
|
+
- Browser requests with an `Origin` header are rejected; non-browser MCP clients are supported.
|
|
77
|
+
- Request bodies are limited to 1 MiB; responses are not cached; request logging is disabled.
|
|
78
|
+
- OpenTunnel terminates TLS on this computer; its relay forwards encrypted traffic, not HTTP plaintext.
|
|
79
|
+
- The hostname is public/discoverable; route names are not authentication. Anyone possessing the gateway key
|
|
80
|
+
can use your Orbit tools with your permissions, including writes. Executor policies do not protect against a leaked key.
|
|
81
|
+
- Tools, schemas, annotations, instructions, structured content, errors and rate limits come from official Orbit.
|
|
82
|
+
Only tools are forwarded; resources/prompts/reverse requests are not exposed. No tool call is automatically replayed.
|
|
83
|
+
- To rotate the gateway key, stop the gateway, remove only `gateway.key`, restart, and update the Executor connection.
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
## Re-login and logout
|
|
81
86
|
|
|
82
|
-
```
|
|
83
|
-
|
|
87
|
+
```bash
|
|
88
|
+
vp exec tlo-mcp login
|
|
89
|
+
vp exec tlo-mcp logout
|
|
84
90
|
```
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
Disable and stop the Pitchfork daemon (or stop the foreground gateway) before either command. After login,
|
|
93
|
+
enable and start it again. Leave it disabled after logout to avoid automatic re-login on repository entry.
|
|
94
|
+
Logout removes local OAuth credentials, not the upstream grant,
|
|
95
|
+
gateway key or tunnel identity. An expired/rejected existing grant fails visibly; re-login explicitly rather than
|
|
96
|
+
silently registering replacement clients. OAuth credential locks fail closed; never remove a live process's lock.
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
1. get_projects() → find folderId
|
|
90
|
-
2. get_tasks(projectId) → optionally find taskId
|
|
91
|
-
3. create_activity(folderId, startDate, durationMinutes, contactId)
|
|
92
|
-
```
|
|
98
|
+
## SDK compatibility patches
|
|
93
99
|
|
|
94
|
-
|
|
100
|
+
The workspace pins `@opentunnel/client` and `@opentunnel/protocol` 0.2.2 to Effect 4.0.1 with scoped overrides.
|
|
101
|
+
Versioned patches under `patches/` update old `effect/unstable/*` imports, use Node's standard WebSocket constructor,
|
|
102
|
+
handle WebSocket errors and replace TypeScript parameter properties with erasable fields.
|
|
103
|
+
The SDK/protocol are bundled into the CLI so published output does not depend on Node loading TypeScript dependencies.
|
|
104
|
+
Re-test these patches when upgrading OpenTunnel. The standalone SDK's original Bun requirement does not apply
|
|
105
|
+
to this patched Node CLI.
|
|
95
106
|
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
```bash
|
|
108
|
+
vp test
|
|
109
|
+
vp run types
|
|
110
|
+
vp run build
|
|
99
111
|
```
|
|
100
|
-
|
|
101
|
-
## License
|
|
102
|
-
|
|
103
|
-
MIT
|
package/dist/bin.mjs
CHANGED
|
@@ -1,2 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{T as e,_ as t,a as n,c as r,d as i,f as a,l as o,n as s,o as c,p as l,u,y as d}from"./TloLive-DHG59kAi.mjs";import*as f from"effect/Effect";import*as p from"effect/Layer";import*as m from"effect/Schema";import*as h from"@effect/platform-node/NodeRuntime";import*as g from"@effect/platform-node/NodeServices";import*as _ from"effect/cli/Command";import*as v from"effect/Logger";import*as y from"effect/ai/McpProtocol";import*as b from"effect/ai/McpServer";import*as x from"effect/ai/Toolkit";import*as S from"effect/ai/Tool";const C=m.Literals([`DRAFT`,`OPEN`,`COMPLETED`,`CLOSED`]),w=m.Literals([`TODO`,`BOARD`,`PROJECT`]),T=m.DateFromString.annotate({description:`ISO 8601 date string (e.g., 2025-01-20T10:00:00Z)`}),E=S.make(`get_projects`,{description:`List all projects. Returns projectId, folderId, client, owner, dates, and budget info. Use to discover projects before drilling into tasks or logging time. Data hierarchy: FOLDER → PROJECT → TASK → ACTIVITY.`,parameters:m.Struct({states:C.pipe(m.Array,m.optional).annotate({description:`Filter by project states: DRAFT, OPEN, COMPLETED, CLOSED. Defaults to DRAFT, OPEN, CLOSED`}),limit:m.optional(m.Finite).annotate({description:`Maximum number of projects to return. Defaults to 100`}),page:m.optional(m.Finite).annotate({description:`Page number for pagination. Defaults to 1`})}),success:m.Struct({projects:m.Array(o)}),failure:e,failureMode:`return`,dependencies:[c]}),D=S.make(`get_project_details`,{description:`Get single project with full details including billing rates and folder info. Requires projectId from get_projects.`,parameters:m.Struct({projectId:m.Finite.annotate({description:`Project ID from get_projects response`})}),success:m.Struct({project:o}),failure:e,failureMode:`return`,dependencies:[c]}),O=S.make(`get_messages`,{description:`Get comments/messages on a project, board, or todo. Use to read discussion history or context about work items.`,parameters:m.Struct({objectType:w.annotate({description:`Type of object: PROJECT (for project comments), BOARD (board-level), TODO (kanban card)`}),objectId:m.Finite.annotate({description:`ID of the project, board, or todo`}),onlyComments:m.optional(m.Boolean).annotate({description:`Only return user comments (exclude system messages). Defaults to true`}),hideInternal:m.optional(m.Boolean).annotate({description:`Hide internal/private messages. Defaults to true`}),limit:m.optional(m.Finite).annotate({description:`Maximum number of messages to return. Defaults to 100`})}),success:m.Struct({messages:m.Array(r)}),failure:e,failureMode:`return`,dependencies:[c]}),k=S.make(`set_task_state`,{description:`Change task state. Typical flow: DRAFT → OPEN → COMPLETED → CLOSED. Requires both projectId and taskId from get_tasks.`,parameters:m.Struct({projectId:m.Finite.annotate({description:`Project ID containing the task (from get_projects)`}),taskId:m.Finite.annotate({description:`Task ID to update (from get_tasks)`}),state:C.annotate({description:`New state: DRAFT, OPEN, COMPLETED, or CLOSED`})}),success:m.Void,failure:e,failureMode:`return`,dependencies:[c]}),A=S.make(`get_tasks`,{description:`Get tasks within a project. Tasks have budget (workload), remaining hours, and state. Use taskId when logging time to attribute work to specific deliverables.`,parameters:m.Struct({projectId:m.Finite.annotate({description:`Project ID from get_projects`}),states:C.pipe(m.Array,m.optional).annotate({description:`Filter by task states: DRAFT, OPEN, COMPLETED, CLOSED. Defaults to DRAFT, OPEN, CLOSED`}),limit:m.optional(m.Finite).annotate({description:`Maximum number of tasks to return. Defaults to 100`}),page:m.optional(m.Finite).annotate({description:`Page number for pagination. Defaults to 1`})}),success:m.Struct({tasks:m.Array(u)}),failure:e,failureMode:`return`,dependencies:[c]}),j=S.make(`get_tasks_for_user`,{description:`Get tasks assigned to a user for a date range. Returns tasks with REMAINING budget hours. Useful for finding what work is available to log time against.`,parameters:m.Struct({contactId:m.String.annotate({description:`User/contact ID to get tasks for`}),startDate:T.annotate({description:`Start date for the query (e.g., 2025-01-20T00:00:00Z)`}),period:m.Finite.annotate({description:`Number of days to include in the query`})}),success:m.Struct({tasks:m.Array(i)}),failure:e,failureMode:`return`,dependencies:[c]}),M=S.make(`get_todo_detail`,{description:`Get kanban card details. Response includes: BOARDLISTS (all columns with IDs for move_todo), LINKS (connected tasks), ACTIVITIES (time logged against this todo).`,parameters:m.Struct({id:m.Finite.annotate({description:`Todo/card ID to retrieve`})}),success:m.Struct({todo:a}),failure:e,failureMode:`return`,dependencies:[c]}),N=S.make(`get_board_todos`,{description:`List todos/cards from a kanban board. Use to find todo IDs by name for use with get_todo_detail. Get boardId from get_todo_detail BOARDID field or project board URL.`,parameters:m.Struct({boardId:m.Finite.annotate({description:`Board ID (from get_todo_detail TODO.BOARDID field or URL)`}),query:m.optional(m.String).annotate({description:`Filter todos by name (case-insensitive partial match)`}),boardListId:m.optional(m.Finite).annotate({description:`Filter by column ID (from BOARDLISTS in get_todo_detail)`}),limit:m.optional(m.Finite).annotate({description:`Maximum results. Defaults to 100`})}),success:m.Struct({todos:m.Array(l)}),failure:e,failureMode:`return`,dependencies:[c]}),P=S.make(`move_todo`,{description:`Move kanban card between columns. Get boardId and boardListId from get_todo_detail response (BOARDLISTS array contains column IDs).`,parameters:m.Struct({id:m.Finite.annotate({description:`Todo/card ID to move`}),boardId:m.Finite.annotate({description:`Board ID (from get_todo_detail)`}),boardListId:m.Finite.annotate({description:`Target column ID (from BOARDLISTS in get_todo_detail)`}),sortIndex:m.Finite.annotate({description:`Position within column (0 = top)`})}),success:m.Void,failure:e,failureMode:`return`,dependencies:[c]}),F=S.make(`post_message`,{description:`Add a comment to a project, board, or todo. Use to leave notes or updates on work items.`,parameters:m.Struct({objectType:w.annotate({description:`Type of object: PROJECT, BOARD, or TODO`}),objectId:m.Finite.annotate({description:`ID of the project, board, or todo to comment on`}),content:m.String.annotate({description:`Comment text to post`})}),success:m.Void,failure:e,failureMode:`return`,dependencies:[c]}),I=S.make(`get_week_activities`,{description:`Get all time entries for the week containing the given date. Returns project/task names, duration, and IDs. Use to review or summarize work. Response includes folderId which can be used for create_activity.`,parameters:m.Struct({date:T.annotate({description:`Any date within target week (e.g., 2025-01-20). Week is Mon-Sun.`}),contactId:m.String.annotate({description:`User/contact ID to get activities for`}),timezone:m.optional(m.String).annotate({description:`Timezone for week boundaries. Defaults to Europe/Amsterdam`})}),success:m.Struct({activities:m.Array(d)}),failure:e,failureMode:`return`,dependencies:[t]}),L=S.make(`create_activity`,{description:`Log time against a project/task. Get folderId from get_projects or get_week_activities. Duration examples: 60=1h, 90=1.5h, 120=2h. Optionally link to a taskId for specific work attribution or todoId for kanban card.`,parameters:m.Struct({folderId:m.String.annotate({description:`Folder/project ID to log time against (from get_projects or get_week_activities)`}),startDate:T.annotate({description:`Date and time of the activity (e.g., 2025-01-20T09:00:00Z). Time is for display.`}),durationMinutes:m.Finite.annotate({description:`Duration in minutes. Examples: 30, 60 (1h), 90 (1.5h), 120 (2h)`}),contactId:m.String.annotate({description:`User/contact ID who performed the work`}),taskId:m.optional(m.String).annotate({description:`Task ID to attribute time to (from get_tasks). Links time to specific deliverable.`}),todoId:m.optional(m.String).annotate({description:`Todo/kanban card ID to link this time entry to`}),description:m.optional(m.String).annotate({description:`Description of work performed`}),clientColor:m.optional(m.String).annotate({description:`Hex color code for display (e.g., #eeeeee)`})}),success:m.Struct({id:m.Finite.annotate({description:`ID of the created activity`})}),failure:e,failureMode:`return`,dependencies:[t]}),R=S.make(`update_activity`,{description:`Modify an existing time entry. Get activity ID from get_week_activities. Only specify fields you want to change.`,parameters:m.Struct({id:m.Finite.annotate({description:`Activity ID to update (from get_week_activities)`}),startDate:m.optional(T).annotate({description:`New date/time for the activity`}),durationMinutes:m.optional(m.Finite).annotate({description:`New duration in minutes`}),description:m.optional(m.String).annotate({description:`New description`})}),success:m.Void,failure:e,failureMode:`return`,dependencies:[t]}),z=S.make(`delete_activity`,{description:`Remove a time entry. Get activity ID from get_week_activities. This action cannot be undone.`,parameters:m.Struct({id:m.Finite.annotate({description:`Activity ID to delete (from get_week_activities)`})}),success:m.Void,failure:e,failureMode:`return`,dependencies:[t]}),B=x.make(E,D,O,A,j,M,N,P,F,k,I,L,R,z),V=B.toLayer({get_projects:f.fn(`TloToolkitHandlers.get_projects`)(function*(e){return{projects:yield*(yield*c).getProjects(e)}}),get_project_details:f.fn(`TloToolkitHandlers.get_project_details`)(function*(e){return{project:yield*(yield*c).getProjectDetails(e)}}),get_messages:f.fn(`TloToolkitHandlers.get_messages`)(function*(e){return{messages:yield*(yield*c).getMessages(e)}}),set_task_state:f.fn(`TloToolkitHandlers.set_task_state`)(function*(e){yield*(yield*c).setTaskState(e)}),get_tasks:f.fn(`TloToolkitHandlers.get_tasks`)(function*(e){return{tasks:yield*(yield*c).getTasks(e)}}),get_tasks_for_user:f.fn(`TloToolkitHandlers.get_tasks_for_user`)(function*(e){return{tasks:yield*(yield*c).getTasksForUser(e)}}),get_todo_detail:f.fn(`TloToolkitHandlers.get_todo_detail`)(function*(e){return{todo:yield*(yield*c).getTodoDetail(e)}}),get_board_todos:f.fn(`TloToolkitHandlers.get_board_todos`)(function*(e){return{todos:yield*(yield*c).getBoardTodos(e)}}),move_todo:f.fn(`TloToolkitHandlers.move_todo`)(function*(e){yield*(yield*c).moveTodo(e)}),post_message:f.fn(`TloToolkitHandlers.post_message`)(function*(e){yield*(yield*c).postMessage(e)}),get_week_activities:f.fn(`TloToolkitHandlers.get_week_activities`)(function*(e){return{activities:yield*(yield*t).getWeek(e.date,e.contactId,e.timezone)}}),create_activity:f.fn(`TloToolkitHandlers.create_activity`)(function*(e){return{id:yield*(yield*t).createActivity(e)}}),update_activity:f.fn(`TloToolkitHandlers.update_activity`)(function*(e){yield*(yield*t).updateActivity(e)}),delete_activity:f.fn(`TloToolkitHandlers.delete_activity`)(function*(e){yield*(yield*t).deleteActivity(e)})}),H=p.merge(v.layer([v.consolePretty()]),p.succeed(v.LogToStderr,!0));function U(e){return b.toolkit(B).pipe(p.provide(V),p.provide(b.layerStdio({...e,protocols:[y.v2025_06_18]})),p.provide(H))}const W=`tlo-mcp`,G=`0.0.0`,K=_.make(W,{},f.fn(`TloMcp.serve`)(function*(){return yield*U({name:W,version:G}).pipe(p.provide(s),p.provide(n),p.launch)}));f.fn(`TloMcp.run`)(function*(e){return yield*_.run(K,{version:G})})(process.argv).pipe(f.provide(g.layer),h.runMain);export{};
|
|
2
|
+
import*as e from"@effect/platform-node/NodeRuntime";import*as t from"@effect/platform-node/NodeServices";import*as n from"effect/Effect";import*as r from"effect/Schema";import*as i from"effect/cli/Command";import*as a from"effect/cli/Flag";import{OAuth as o,Pkce as s}from"@yielded/oauth";import*as c from"effect/Clock";import*as l from"effect/Context";import*as u from"effect/Crypto";import*as d from"effect/Equal";import*as f from"effect/Exit";import*as p from"effect/Layer";import*as m from"effect/Match";import*as h from"effect/Redacted";import*as ee from"effect/Semaphore";import{FetchHttpClient as g,HttpClient as _,HttpClientRequest as v,HttpClientResponse as te,HttpIncomingMessage as ne,HttpRouter as y,HttpServer as re,HttpServerRequest as b,HttpServerResponse as x}from"effect/http";import{ChildProcess as S,ChildProcessSpawner as C}from"effect/process";import{createHash as ie,randomBytes as ae,timingSafeEqual as oe}from"node:crypto";import{createServer as se}from"node:http";import*as ce from"@effect/platform-node/NodeHttpServer";import*as w from"effect/Deferred";import*as le from"effect/Predicate";import*as ue from"node:os";import{homedir as de}from"node:os";import*as fe from"effect/FileSystem";import*as pe from"effect/Path";import*as T from"effect/Stream";import{Context as me,Effect as E,Layer as D,Option as he,Queue as O,Schema as k,Stream as ge}from"effect";import{HttpApi as _e,HttpApiClient as ve,HttpApiEndpoint as A,HttpApiGroup as ye,HttpApiMiddleware as be,HttpApiSchema as xe,HttpApiSecurity as Se,OpenApi as Ce}from"effect/http-api";import*as j from"node:fs/promises";import*as M from"node:path";import*as we from"node:net";import{Duplex as Te}from"node:stream";import*as Ee from"node:tls";import*as De from"effect/ByteSize";import*as N from"effect/ai/McpProtocol";import*as Oe from"effect/ai/McpServer";import*as ke from"effect/Option";import*as Ae from"effect/Struct";import*as P from"effect/ai/McpSchema";import*as je from"effect/encoding/Sse";const Me=`https://api.orbit.teamleader.eu`,F=`${Me}/mcp`,Ne=r.RedactedFromValue(r.String.check(r.isMinLength(1))),Pe=r.Struct({clientId:r.String.check(r.isMinLength(1)),clientSecret:r.optionalKey(Ne),accessToken:Ne,refreshToken:r.optionalKey(Ne),expiresAt:r.optionalKey(r.Finite)});var I=class extends r.TaggedError()(`OrbitAuthError`,{message:r.String}){};function Fe(e,t,n){let r=t.refreshToken??e.refreshToken;return{clientId:e.clientId,...e.clientSecret===void 0?{}:{clientSecret:e.clientSecret},accessToken:t.accessToken,...r===void 0?{}:{refreshToken:r},...t.expiresIn===void 0?{}:{expiresAt:n+t.expiresIn*1e3}}}function Ie(e,t){return e.expiresAt===void 0||e.expiresAt<=t+6e4}function Le(e){return e.clientSecret===void 0?{method:`none`,publicClient:!0}:{method:`client_secret_post`,secret:e.clientSecret.pipe(h.value,h.make)}}function Re(e,t){let n=e.searchParams.get(`state`);if(n===null||e.pathname!==`/callback`||e.searchParams.getAll(`state`).length!==1)return;let r=Buffer.from(t),i=Buffer.from(n);if(r.length!==i.length||!oe(r,i))return;if(e.searchParams.has(`error`))throw Error(`Orbit authorization was denied.`);let a=e.searchParams.get(`code`);if(a!==null&&a.length!==0&&e.searchParams.getAll(`code`).length===1)return a}const ze=n.fn(`OrbitAuth.listenForLogin`)(function*(e){let t=yield*w.make(),r=yield*n.sync(se),i=yield*ce.make(()=>r,{port:0,host:`127.0.0.1`}).pipe(n.mapError(()=>I.make({message:`Could not bind OAuth callback.`})));if(!le.isTagged(i.address,`InetAddressV4`))return yield*I.make({message:`Could not bind OAuth callback.`});let a=`127.0.0.1:${i.address.port}`,o=`http://${a}/callback`,s={"content-type":`text/plain; charset=utf-8`,"cache-control":`no-store`,"referrer-policy":`no-referrer`,"content-security-policy":`default-src 'none'`},c=x.text(`Invalid callback.`,{status:400,headers:s}),l=x.text(`Authorization received. Return to your terminal to complete login.`,{headers:s}),u=x.text(`Authorization denied. Return to your terminal.`,{status:400,headers:s}),d=n.fn(`OrbitAuth.handleCallback`)(function*(){let r=yield*b.HttpServerRequest;return r.method!==`GET`||r.headers.host!==a||(yield*w.isDone(t))?c:yield*n.try({try:()=>Re(new URL(r.url,o),e),catch:()=>I.make({message:`Orbit authorization was denied.`})}).pipe(n.matchEffect({onFailure:e=>w.fail(t,e).pipe(n.map(e=>e?u:c)),onSuccess:e=>e===void 0?n.succeed(c):w.succeed(t,e).pipe(n.map(e=>e?l:c))}))}),f=yield*y.toHttpEffect(y.add(`*`,`*`,d()));return yield*re.serveEffect(f).pipe(n.provideService(re.HttpServer,i)),yield*n.addFinalizer(()=>n.sync(()=>r.closeAllConnections())),{redirectUri:o,code:w.await(t).pipe(n.timeout(`3 minutes`),n.mapError(()=>I.make({message:`Login denied or timed out. Run tlo-mcp login again.`})))}}),Be=r.fromJsonString(Pe),Ve=`@2digits/tlo-mcp/orbit`;var L=class extends l.Service()(`@2digits/tlo-mcp/oauth/store/SessionStore`){};function He(e){return`"${e.replaceAll(`\\`,String.raw`\\`).replaceAll(`"`,String.raw`\"`)}"`}function Ue(e={}){return p.effect(L,n.gen(function*(){let t=yield*fe.FileSystem,i=yield*pe.Path,a=yield*C.ChildProcessSpawner,o=e.platform??process.platform,s=e.directory??i.join(de(),`.config`,`2digits`,`tlo-mcp`),c=i.join(s,`session.json`),l=i.join(s,`session.lock`),u=t.makeDirectory(s,{recursive:!0,mode:448}).pipe(n.andThen(t.chmod(s,448))),d=n.fn(`SessionStore.security`)(function*(e,t){let r=yield*a.spawn(S.make(`/usr/bin/security`,e,{stdin:t===void 0?`ignore`:T.succeed(new TextEncoder().encode(t)),stderr:`ignore`}));return yield*n.all({output:r.stdout.pipe(T.decodeText(),T.mkString),exitCode:r.exitCode},{concurrency:`unbounded`})},n.scoped),f=n.fn(`SessionStore.load`)(function*(){let e;if(o===`darwin`){let t=yield*d([`find-generic-password`,`-a`,`orbit`,`-s`,Ve,`-w`]);if(t.exitCode===44)return;if(t.exitCode!==0)return yield*I.make({message:`Could not read macOS Keychain.`});e=t.output.trim()}else{let r=yield*t.readFileString(c).pipe(n.catchReason(`PlatformError`,`NotFound`,()=>n.void));if(r===void 0)return;e=r}return yield*r.decodeEffect(Be)(e)},n.mapError(()=>I.make({message:`Could not read credentials. Run tlo-mcp login again.`}))),p=n.fn(`SessionStore.save`)(function*(e){let n=yield*r.encodeEffect(Be)(e);if(o===`darwin`){if((yield*d([`-q`,`-i`],`add-generic-password -U -a orbit -s ${He(Ve)} -w ${He(n)}\n`)).exitCode!==0)return yield*I.make({message:`Could not save macOS Keychain credentials.`})}else{yield*u;let e=yield*t.makeTempDirectoryScoped({directory:s,prefix:`session-`}),r=i.join(e,`session.json`);yield*t.writeFileString(r,n,{mode:384}),yield*t.rename(r,c)}},n.scoped,n.mapError(()=>I.make({message:`Could not persist OAuth credentials. Run tlo-mcp login again.`}))),m=n.fn(`SessionStore.clear`)(function*(){if(o===`darwin`){let e=yield*d([`delete-generic-password`,`-a`,`orbit`,`-s`,Ve]);if(e.exitCode!==0&&e.exitCode!==44)return yield*I.make({message:`Could not remove Keychain credentials.`})}else yield*t.remove(c,{force:!0})},n.mapError(()=>I.make({message:`Could not remove OAuth credentials.`}))),h=n.fn(`SessionStore.lock`)(function*(){yield*u,yield*n.acquireRelease(t.writeFileString(l,String(process.pid),{flag:`wx`,mode:384}).pipe(n.catchReason(`PlatformError`,`AlreadyExists`,()=>I.make({message:`OAuth credentials locked. Close other login/refresh processes. After a crash only, remove ${l}.`}))),()=>t.remove(l).pipe(n.orDie))},n.mapError(e=>r.is(I)(e)?e:I.make({message:`Could not acquire OAuth credential lock. Check directory permissions.`})));return L.of({load:f(),save:p,clear:m(),lock:h()})}))}const We=Ue(),Ge=r.Struct({client_id:r.String.check(r.isMinLength(1)),client_secret:r.String.check(r.isMinLength(1)).pipe(r.RedactedFromValue,r.optionalKey)});var R=class extends l.Service()(`@2digits/tlo-mcp/oauth/OrbitAuth`){};const Ke=p.effect(R,n.gen(function*(){let e=yield*L,t=yield*u.Crypto,i=yield*_.HttpClient,a=yield*C.ChildProcessSpawner,l=yield*ee.make(1),p=yield*n.fn(`OrbitAuth.discover`)(function*(){let e=yield*_.withScope(i).get(`${Me}/.well-known/oauth-authorization-server`);if(e.status!==200)return yield*I.make({message:`Could not discover Orbit OAuth endpoints.`});let t=yield*e.json.pipe(n.flatMap(r.decodeUnknownEffect(o.Metadata)));return t.issuer!==`https://api.orbit.teamleader.eu`||t.authorization_endpoint!==`https://api.orbit.teamleader.eu/oauth2/Authorize`||t.token_endpoint!==`https://api.orbit.teamleader.eu/oauth2/access_token`?yield*I.make({message:`Unexpected Orbit endpoints; refusing to send credentials.`}):t},n.scoped,n.timeout(`15 seconds`),n.mapError(()=>I.make({message:`Invalid or unavailable Orbit OAuth metadata.`})))().pipe(n.cachedWithTTL(e=>f.isSuccess(e)?1/0:0)),te=n.fn(`OrbitAuth.accessToken`)(function*(){let t=yield*e.load;if(t===void 0)return yield*I.make({message:`Not authenticated. Run tlo-mcp login first.`});if(!Ie(t,yield*c.currentTimeMillis))return t.accessToken;if(t.refreshToken===void 0)return t.expiresAt===void 0?t.accessToken:yield*I.make({message:`Orbit session expired without a refresh token. Run tlo-mcp login.`});let r=yield*p,i=Fe(t,yield*(yield*o.make({metadata:r,clientId:t.clientId,authentication:Le(t),timeoutMs:15e3})).refreshGrant({refreshToken:t.refreshToken,resources:[F]}).pipe(n.flatMap(o.tokens)),yield*c.currentTimeMillis);return yield*e.save(i),i.accessToken}),ne=n.fn(`OrbitAuth.register`)(function*(e){let t=yield*v.post(`${Me}/oauth2/register`).pipe(v.bodyJson({client_name:`@2digits/tlo-mcp`,redirect_uris:[e],response_types:[`code`],grant_types:[`authorization_code`,`refresh_token`],token_endpoint_auth_method:`none`}),n.flatMap(_.withScope(i).execute));return t.status!==200&&t.status!==201?yield*I.make({message:`Orbit client registration failed (HTTP ${t.status}).`}):yield*t.json.pipe(n.flatMap(r.decodeUnknownEffect(Ge)))},n.scoped,n.timeout(`15 seconds`)),y=n.fn(`OrbitAuth.openBrowser`)(function*(e){return yield*(yield*m.value(process.platform).pipe(m.when(`darwin`,()=>S.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`open ${h.value(e)}`),m.when(`win32`,()=>S.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`rundll32 url.dll,FileProtocolHandler ${h.value(e)}`),m.orElse(()=>S.make({stdout:`ignore`,stderr:`ignore`,forceKillAfter:`1 second`})`xdg-open ${h.value(e)}`))).exitCode},n.scoped,n.timeout(`5 seconds`),n.filterOrFail(d.equals(C.ExitCode(0)),()=>I.make({message:`Could not open browser.`})),(e,t)=>e.pipe(n.catch(()=>n.sync(()=>{process.stderr.write(`Open this URL in your browser:\n${h.value(t)}\n`)})))),re=n.fn(`OrbitAuth.login`)(function*(){let t=yield*p,r=yield*s.random(),i=yield*s.make(),a=yield*r.pipe(h.value,ze),l=yield*ne(a.redirectUri),u={clientId:l.client_id,...l.client_secret===void 0?{}:{clientSecret:l.client_secret}},d=yield*o.make({metadata:t,...u,authentication:Le(u),timeoutMs:15e3}),f=yield*d.authorizationUrl({redirectUri:a.redirectUri,scopes:[],state:r,codeChallenge:i.challenge,resources:[F]});yield*y(f);let m=yield*a.code,ee=yield*d.codeGrant({code:h.make(m),redirectUri:a.redirectUri,pkceVerifier:i.verifier,resources:[F]}).pipe(n.flatMap(o.tokens));yield*e.save(Fe(u,ee,yield*c.currentTimeMillis)),yield*n.sync(()=>{process.stderr.write(`Orbit login complete.
|
|
3
|
+
`)})}),b=n.fn(`OrbitAuth.locked`)(function*(t){return yield*e.lock,yield*t},n.scoped,l.withPermit,n.provideService(_.HttpClient,i),n.provideService(u.Crypto,t),n.provideService(C.ChildProcessSpawner,a),n.provideService(g.RequestInit,{redirect:`error`}),n.mapError(e=>r.is(I)(e)?e:I.make({message:`Orbit authentication unavailable. Check connectivity or run tlo-mcp login again.`})));return R.of({accessToken:b(te()),login:b(re()),logout:b(e.clear)})})).pipe(p.provide(We),p.provide(g.layer)),qe=k.String.pipe(k.brand(`CSR.Raw`)),Je=k.String.pipe(k.brand(`Hostname`));function Ye(){return(e,t)=>{class n{static makeUnsafe(e){return e}}return Object.setPrototypeOf(n,t),n}}var Xe=class extends Ye()(`CertificateID`,k.String){};k.String.pipe(k.brand(`ChallengeToken`));var Ze=class extends k.Class(`Certificate/StateChallenge`)({type:k.Literal(`challenge`),token:k.String,key:k.String}){},Qe=class extends k.Class(`Certificate/StateIssuing`)({type:k.Literal(`issuing`)}){},$e=class extends k.Class(`Certificate/StateReady`)({type:k.Literal(`ready`),certificate:k.String,chain:k.String,expiry:k.String}){},et=class extends k.Class(`Certificate/StateFailed`)({type:k.Literal(`failed`),reason:k.String}){};const tt=k.Union([Ze,Qe,$e,et]);var nt=class extends k.Class(`Certificate`)({id:Xe,state:tt}){},z=class extends Ye()(`TunnelID`,k.String){};const B=k.String.pipe(k.brand(`TunnelToken`));k.TaggedError()(`NotFound`,{tunnelID:z}),k.TaggedError()(`NoCertificate`,{tunnelID:z}),k.TaggedError()(`CertificateNotReady`,{tunnelID:z,currentState:k.String}),k.TaggedError()(`InvalidHostname`,{provided:Je,expected:Je});const rt=k.Literals([`offline`,`online`]);var it=class extends k.Class(`Tunnel/Info`)({id:z,hostname:Je,state:rt,certificateID:Xe.pipe(k.optional)}){},at=`0.2.2`,ot=class extends k.TaggedError()(`InvalidRequestError`,{message:k.String},{httpApiStatus:400}){},st=class extends k.TaggedError()(`UnauthorizedError`,{message:k.String},{httpApiStatus:401}){},V=class extends k.TaggedError()(`TunnelNotFoundError`,{tunnelID:k.String,message:k.String},{httpApiStatus:404}){},ct=class extends k.TaggedError()(`CertificateNotFoundError`,{tunnelID:k.String,message:k.String},{httpApiStatus:404}){},lt=class extends k.TaggedError()(`InvalidHostnameError`,{provided:k.String,expected:k.String,message:k.String},{httpApiStatus:400}){},ut=class extends k.TaggedError()(`HostnameUnavailableError`,{name:k.String,message:k.String},{httpApiStatus:409}){},dt=class extends k.TaggedError()(`CertificateInProgressError`,{tunnelID:k.String,message:k.String},{httpApiStatus:409}){},H=class extends k.TaggedError()(`ServiceUnavailableError`,{message:k.String},{httpApiStatus:503}){};me.Service()(`@opentunnel/protocol/OpenTunnelAuthorizationToken`);var U=class extends be.Service()(`@opentunnel/protocol/OpenTunnelAuthorization`,{security:{bearer:Se.bearer},error:st}){};const ft=k.Struct({name:k.String.pipe(k.optional)}),pt=k.Struct({tunnel:it,token:B}).pipe(xe.status(201)),mt=ye.make(`tunnel`).add(A.post(`tunnel.create`,`/api/tunnel`,{payload:ft,success:pt,error:[ot,ut,H]})).add(A.get(`tunnel.get`,`/api/tunnel/:id`,{params:k.Struct({id:z}),success:it,error:[V,H]}).middleware(U)).add(A.post(`tunnel.bindCertificate`,`/api/tunnel/:id/certificate`,{params:k.Struct({id:z}),payload:k.Struct({csr:qe}),success:nt.pipe(xe.status(202)),error:[ot,lt,V,dt,H]}).middleware(U)).add(A.get(`tunnel.getCertificate`,`/api/tunnel/:id/certificate`,{params:k.Struct({id:z}),success:nt,error:[V,ct,H]}).middleware(U)).add(A.delete(`tunnel.remove`,`/api/tunnel/:id`,{params:k.Struct({id:z}),success:xe.NoContent,error:[V,H]}).middleware(U)).add(A.get(`tunnel.connect`,`/api/tunnel/:id/connect`,{params:k.Struct({id:z}),success:k.Boolean,error:[V]}).annotateMerge(Ce.annotations({summary:`Connect a bridge WebSocket`,description:`This endpoint upgrades to the opentunnel WebSocket protocol.`}))).annotateMerge(Ce.annotations({title:`tunnel`})),ht=_e.make(`opentunnel`).add(mt).annotateMerge(Ce.annotations({title:`OpenTunnel API`,version:`0.1.0`,description:`Create and manage blind TLS tunnels.`})),gt=`opentunnel-sdk/${at}`;var _t=class e extends me.Service()(`@opentunnel/client/OpenTunnelApiClient`){static layer(t){return D.effect(e,E.gen(function*(){let e=(yield*_.HttpClient).pipe(_.mapRequest(v.setHeader(`user-agent`,gt)));return{client:yield*ve.makeWith(ht,{baseUrl:t.api,httpClient:e}),authorized:n=>ve.makeWith(ht,{baseUrl:t.api,httpClient:e.pipe(_.mapRequest(v.bearerToken(n)))})}})).pipe(D.provide(g.layer))}};const W=(e,...t)=>{let n=vt(t),r=n.length<128?[n.length]:(()=>{let e=[];for(let t=n.length;t>0;t>>=8)e.unshift(t&255);return[128|e.length,...e]})();return vt([new Uint8Array([e,...r]),n])},vt=e=>{let t=new Uint8Array(e.reduce((e,t)=>e+t.length,0)),n=0;for(let r of e)t.set(r,n),n+=r.length;return t},G=(...e)=>W(48,...e),yt=(...e)=>W(49,...e),bt=e=>W(12,new TextEncoder().encode(e)),xt=e=>W(130,new TextEncoder().encode(e)),K=e=>{let[t,n,...r]=e.split(`.`).map(Number),i=[t*40+n];for(let e of r){let t=[e&127];for(let n=e>>7;n>0;n>>=7)t.unshift(n&127|128);i.push(...t)}return W(6,new Uint8Array(i))},St=e=>{let t=0;for(;t<e.length-1&&e[t]===0;)t++;let n=e.subarray(t);return W(2,n[0]&128?vt([new Uint8Array([0]),n]):n)};async function Ct(e,t){let n=new Uint8Array(await crypto.subtle.exportKey(`spki`,t.publicKey)),r=G(W(2,new Uint8Array([0])),G(yt(G(K(`2.5.4.3`),bt(e)))),n,W(160,G(K(`1.2.840.113549.1.9.14`),yt(G(G(K(`2.5.29.17`),W(4,G(xt(e),xt(`*.${e}`))))))))),i=new Uint8Array(await crypto.subtle.sign({name:`ECDSA`,hash:`SHA-256`},t.privateKey,r)),a=G(St(i.subarray(0,32)),St(i.subarray(32))),o=G(r,G(K(`1.2.840.10045.4.3.2`)),W(3,new Uint8Array([0]),a));return`-----BEGIN CERTIFICATE REQUEST-----\n${Buffer.from(o).toString(`base64`).replace(/.{1,64}/g,`$&
|
|
4
|
+
`)}-----END CERTIFICATE REQUEST-----`}var wt=class extends k.TaggedError()(`OpenTunnelClientError`,{message:k.String,cause:k.optional(k.Defect())}){},q=class extends k.TaggedError()(`OpenTunnelStorageError`,{message:k.String,cause:k.optional(k.Defect())}){};function Tt(e={}){let t=e.env??process.env,n=e.home??ue.homedir(),r=t.XDG_DATA_HOME??M.join(n,`.local`,`share`),i=M.join(r,`opentunnel`),a=e=>M.join(i,e),o=e=>E.tryPromise({try:()=>j.readFile(e,`utf8`),catch:e=>e}).pipe(E.catch(t=>E.gen(function*(){if(!(typeof t==`object`&&t&&`code`in t&&t.code===`ENOENT`))return yield*new q({message:`Failed to read ${e}`,cause:t})}))),s=E.fn(`OpenTunnelStorage.profiles`)(function*(){return(yield*E.tryPromise({try:()=>j.readdir(i,{withFileTypes:!0}),catch:e=>e}).pipe(E.catch(e=>E.gen(function*(){return typeof e==`object`&&e&&`code`in e&&e.code===`ENOENT`?[]:yield*new q({message:`Failed to list ${i}`,cause:e})})))).filter(e=>e.isDirectory()).map(e=>e.name).sort()}),c=E.fn(`OpenTunnelStorage.load`)(function*(e){let t=a(e),n=yield*o(M.join(t,`tunnel.json`));if(!n)return;let[r,i,s,c]=yield*E.all([o(M.join(t,`token`)),o(M.join(t,`private-key.pem`)),o(M.join(t,`certificate.pem`)),o(M.join(t,`chain.pem`))]);if(!r||!i||!s||c===void 0)return yield*new q({message:`Incomplete tunnel identity for ${e}`});let l=JSON.parse(n);return{id:l.id,hostname:l.hostname,token:r.trim(),privateKey:i,certificate:s,chain:c,certificateExpiry:new Date(l.certificateExpiry)}}),l=E.fn(`OpenTunnelStorage.loadPending`)(function*(e){let t=a(e),n=yield*o(M.join(t,`pending.json`));if(!n)return;let[r,i]=yield*E.all([o(M.join(t,`token`)),o(M.join(t,`private-key.pem`))]);if(!r||!i)return yield*new q({message:`Incomplete pending tunnel identity for ${e}`});let s=JSON.parse(n);return{id:s.id,hostname:s.hostname,token:r.trim(),privateKey:i,csr:s.csr}});return{profiles:()=>s(),load:e=>c(e),loadPending:e=>l(e),savePending:(e,t)=>E.tryPromise({try:async()=>{let n=a(e);await j.mkdir(n,{recursive:!0,mode:448}),await Promise.all([j.writeFile(M.join(n,`pending.json`),JSON.stringify({id:t.id,hostname:t.hostname,csr:t.csr},null,2),{mode:384}),j.writeFile(M.join(n,`token`),`${t.token}\n`,{mode:384}),j.writeFile(M.join(n,`private-key.pem`),t.privateKey,{mode:384})])},catch:t=>new q({message:`Failed to save pending tunnel identity for ${e}`,cause:t})}),save:(e,t)=>E.tryPromise({try:async()=>{let n=a(e);await j.mkdir(n,{recursive:!0,mode:448}),await Promise.all([j.writeFile(M.join(n,`tunnel.json`),JSON.stringify({id:t.id,hostname:t.hostname,certificateExpiry:t.certificateExpiry.toISOString()},null,2),{mode:384}),j.writeFile(M.join(n,`token`),`${t.token}\n`,{mode:384}),j.writeFile(M.join(n,`private-key.pem`),t.privateKey,{mode:384}),j.writeFile(M.join(n,`certificate.pem`),t.certificate,{mode:384}),j.writeFile(M.join(n,`chain.pem`),t.chain,{mode:384})]),await j.rm(M.join(n,`pending.json`),{force:!0})},catch:t=>new q({message:`Failed to save tunnel identity for ${e}`,cause:t})}),remove:e=>E.tryPromise({try:()=>j.rm(a(e),{recursive:!0,force:!0}),catch:t=>new q({message:`Failed to remove tunnel identity for ${e}`,cause:t})}),list:()=>s().pipe(E.flatMap(e=>E.forEach(e,e=>c(e).pipe(E.map(t=>t?{profile:e,tunnel:t}:void 0)))),E.map(e=>e.filter(e=>e!==void 0)))}}const Et=k.Int.check(k.isBetween({minimum:0,maximum:4294967295}));var Dt=class extends k.Class(`BridgeProtocol/ClientInfo`)({version:k.String,max_conns:k.Number}){},Ot=class extends k.Class(`BridgeProtocol/AttachMessage`)({type:k.Literal(`attach`),token:k.String,transport:k.Literal(`ws`),client:Dt,routes:k.Array(k.String).pipe(k.optional)}){},kt=class extends k.Class(`BridgeProtocol/AttachedMessage`)({type:k.Literal(`attached`),session:k.String,routes:k.Array(k.String),heartbeat_ms:k.Number,idle_timeout_ms:k.Number}){},At=class extends k.Class(`BridgeProtocol/AttachErrorMessage`)({type:k.Literal(`attach_error`),code:k.String}){},jt=class extends k.Class(`BridgeProtocol/PingMessage`)({type:k.Literal(`ping`),time_sent:k.Number}){},Mt=class extends k.Class(`BridgeProtocol/PongMessage`)({type:k.Literal(`pong`),time_sent:k.Number}){},Nt=class extends k.Class(`BridgeProtocol/DrainMessage`)({type:k.Literal(`drain`),reason:k.String}){},Pt=class extends k.Class(`BridgeProtocol/OpenMessage`)({type:k.Literal(`open`),conn:Et,peer:k.String,sni:k.String,alpn:k.String}){},Ft=class extends k.Class(`BridgeProtocol/EndMessage`)({type:k.Literal(`end`),conn:Et}){},It=class extends k.Class(`BridgeProtocol/ResetMessage`)({type:k.Literal(`reset`),conn:Et,code:k.String}){};k.Union([Ot,jt,Mt,Nt,Ft,It]);const Lt=k.Union([kt,At,jt,Mt,Nt,Pt,Ft,It]),Rt=k.decodeUnknownOption(Lt);function zt(e){try{return Rt(JSON.parse(e))}catch{return he.none()}}k.Union([Ot,kt,At,jt,Mt,Nt,Pt,Ft,It]);const J={BAD_ATTACH:`bad_attach`,BAD_TOKEN:`bad_token`,TUNNEL_NOT_BOUND:`tunnel_not_bound`,CERT_NOT_READY:`cert_not_ready`,REPLACED:`replaced`,UPSTREAM_CONNECT_FAILED:`upstream_connect_failed`,UPSTREAM_IO_ERROR:`upstream_io_error`,CLIENT_IO_ERROR:`client_io_error`,IDLE_TIMEOUT:`idle_timeout`,ROUTE_CONFLICT:`route_conflict`,INVALID_ROUTE:`invalid_route`,UNKNOWN_ROUTE:`unknown_route`},Bt={HEARTBEAT_MS:15e3,IDLE_TIMEOUT_MS:45e3,CONNECT_TIMEOUT_MS:1e4,RECONNECT_BACKOFF_MIN_MS:250,RECONNECT_BACKOFF_MAX_MS:3e4},Y={CONN_ID_SIZE:4,MAX_PAYLOAD_SIZE:32768};function Vt(e){return e.length<Y.CONN_ID_SIZE?null:{conn:new DataView(e.buffer,e.byteOffset,e.byteLength).getUint32(0,!1),payload:e.slice(Y.CONN_ID_SIZE)}}function Ht(e,t){let n=new Uint8Array(Y.CONN_ID_SIZE+t.length);return new DataView(n.buffer).setUint32(0,e,!1),n.set(t,Y.CONN_ID_SIZE),n}const Ut=/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/,Wt=e=>e===`@`||Ut.test(e),Gt=(e,t)=>{let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return`@`;let i=`.${r}`;if(!n.endsWith(i))return;let a=n.slice(0,-i.length);return Ut.test(a)?a:void 0},Kt=e=>{let t=e.lastIndexOf(`:`);if(t===-1)return;let n=e.slice(t+1);if(!/^\d{1,5}$/.test(n))return;let r=Number(n);if(r<1||r>65535)return;let i=e.slice(0,t);if(i.startsWith(`[`)){if(!i.endsWith(`]`))return;i=i.slice(1,-1)}if(i&&/^[A-Za-z0-9.\-:_]+$/.test(i))return{host:i,port:r}};var qt=class extends Error{code;constructor(e){super(`Bridge attach failed: ${e}`),this.code=e}};const Jt=/* @__PURE__ */ new Set([J.BAD_TOKEN,J.CERT_NOT_READY]),Yt=e=>{for(let[t,n]of Object.entries(e)){if(!Wt(t))throw Error(`Invalid route name '${t}'`);if(!Kt(n))throw Error(`Invalid target '${n}' for route '${t}': use host:port`)}},Xt=e=>Ee.createSecureContext({key:e.privateKey,cert:`${e.certificate}\n${e.chain}`}),Zt=(e,t)=>{let n=Object.keys(e).sort(),r=Object.keys(t).sort();return n.length===r.length&&n.every((e,t)=>e===r[t])},Qt=e=>{let t=Math.min(Bt.RECONNECT_BACKOFF_MIN_MS*2**Math.min(e,16),Bt.RECONNECT_BACKOFF_MAX_MS);return Math.round(t/2+t/2*Math.random())};var $t=class{routes;identity;secureContext;refreshTimer;session;shutdown=!1;wake;status;ready;closed;options;constructor(e){this.options=e,Yt(e.routes),this.routes={...e.routes},this.identity=e.identity,this.secureContext=Xt(e.identity),this.status={state:`connecting`,hostname:e.identity.hostname,routes:this.routes,connections:0};let t,n;this.ready=new Promise((e,r)=>{t=e,n=r}),this.closed=this.run(t).then(()=>t(),e=>{throw n(e),e}),this.ready.catch(()=>void 0),this.refreshCertificate()}async refreshCertificate(){if(this.shutdown)return;try{let e=new URL(`/api/tunnel/${encodeURIComponent(this.identity.id)}/certificate`,this.options.api),t=await fetch(e,{headers:{authorization:`Bearer ${this.identity.token}`}}),n=(t.ok?await t.json():void 0)?.state;if(n?.type===`ready`&&n.certificate&&n.expiry&&n.certificate!==this.identity.certificate){let e={...this.identity,certificate:n.certificate,chain:n.chain??``,certificateExpiry:new Date(n.expiry)};this.secureContext=Xt(e),this.identity=e,this.options.onRenewed?.(e),this.emit({type:`certificate-renewed`,expiry:n.expiry})}}catch{}if(this.shutdown)return;let e=this.identity.certificateExpiry.getTime()-Date.now()<2592e6;this.refreshTimer=setTimeout(()=>void this.refreshCertificate(),e?6e4:432e5),this.refreshTimer.unref?.()}getStatus(){return{...this.status,routes:{...this.status.routes}}}setRoutes(e){Yt(e);let t=this.routes;this.routes={...e},this.status.routes=this.routes,Zt(t,this.routes)||this.session?.end({type:`routes-changed`}),this.wake?.()}close(){return this.shutdown=!0,clearTimeout(this.refreshTimer),this.session?.end({type:`shutdown`}),this.wake?.(),this.closed.catch(()=>void 0)}emit(e){this.options.onEvent(e)}sleep(e){return new Promise(t=>{let n=setTimeout(r,e);function r(){clearTimeout(n),t()}this.wake=r}).finally(()=>{this.wake=void 0})}async run(e){let t=0;try{for(;!this.shutdown;){if(Object.keys(this.routes).length===0){this.status.state=`waiting-routes`,await this.sleep(2**31-1);continue}this.status.state=`connecting`,this.emit({type:`connecting`,attempt:t});let n=new en(this.options,this.routes,()=>this.secureContext,{emit:e=>this.emit(e),target:e=>this.routes[e],attached:t=>{this.status.state=`connected`,this.status.session=t,this.status.lastError=void 0,this.status.connectedAt=Date.now(),e()},connections:e=>{this.status.connections=e}});this.session=n;let r=await n.run();if(this.session=void 0,this.status.session=void 0,this.status.connections=0,r.type===`shutdown`)break;if(r.type===`fatal`)throw r.error;if(r.type===`routes-changed`){this.emit({type:`disconnected`,reason:`routes changed`}),t=0;continue}r.attached&&(t=0),this.emit({type:`disconnected`,reason:r.error});let i=Qt(t);t++,this.status.state=`reconnecting`,this.status.lastError=r.error,this.emit({type:`reconnecting`,attempt:t,delayMs:i}),await this.sleep(i)}this.status.state=`stopped`,this.emit({type:`stopped`})}catch(e){throw this.status.state=`stopped`,this.status.lastError=e instanceof Error?e.message:String(e),this.emit({type:`stopped`,error:this.status.lastError}),e}}},en=class{socket;finish;channels=/* @__PURE__ */ new Map;drainWaiters=[];drainTimer;heartbeat;attachTimeout;lastReceived=Date.now();attached=!1;options;routes;secureContext;hooks;constructor(e,t,n,r){this.options=e,this.routes=t,this.secureContext=n,this.hooks=r}run(){return new Promise(e=>{this.finish=e;let t=new URL(`/api/tunnel/${encodeURIComponent(this.options.identity.id)}/connect`,this.options.api);t.protocol=t.protocol===`https:`?`wss:`:`ws:`;let n=new WebSocket(t,[`opentunnel`]);n.binaryType=`arraybuffer`,this.socket=n,this.attachTimeout=setTimeout(()=>this.end({type:`retry`,error:`bridge attach timed out`,attached:!1}),Bt.CONNECT_TIMEOUT_MS),n.addEventListener(`open`,()=>{this.sendControl({type:`attach`,token:this.options.identity.token,transport:`ws`,routes:Object.keys(this.routes).sort(),client:{version:`0.1.0`,max_conns:256}})}),n.addEventListener(`message`,e=>{this.lastReceived=Date.now(),typeof e.data==`string`?this.onControl(e.data):this.onData(new Uint8Array(e.data))}),n.addEventListener(`close`,e=>{let t=`${e.code} ${e.reason}`.trim();this.end({type:`retry`,error:`bridge closed: ${t}`,attached:this.attached})}),n.addEventListener(`error`,()=>{this.end({type:`retry`,error:`bridge WebSocket failed`,attached:this.attached})})})}end(e){let t=this.finish;if(t){this.finish=void 0,clearTimeout(this.attachTimeout),clearInterval(this.heartbeat),clearInterval(this.drainTimer);for(let e of this.drainWaiters.splice(0))e();for(let e of[...this.channels.values()])e.destroy(`bridge disconnected`);this.socket&&this.socket.readyState<=WebSocket.OPEN&&this.socket.close(1e3),t(e)}}onControl(e){let t=zt(e);if(he.isNone(t))return;let n=t.value;if(!this.attached){if(n.type===`attached`){this.attached=!0,clearTimeout(this.attachTimeout);let e=Math.max(n.heartbeat_ms,1e3),t=Math.max(n.idle_timeout_ms,5e3);this.heartbeat=setInterval(()=>{if(Date.now()-this.lastReceived>t){this.end({type:`retry`,error:`bridge idle timeout`,attached:!0});return}this.sendControl({type:`ping`,time_sent:Date.now()})},e),this.hooks.attached(n.session),this.hooks.emit({type:`connected`,session:n.session,routes:[...n.routes]})}else n.type===`attach_error`&&this.end(Jt.has(n.code)?{type:`fatal`,error:new qt(n.code)}:{type:`retry`,error:`attach failed: ${n.code}`,attached:!1});return}switch(n.type){case`open`:return this.open(n.conn,n.sni,n.peer);case`end`:return this.channels.get(n.conn)?.endInbound();case`reset`:return this.channels.get(n.conn)?.destroy(n.code,!1);case`ping`:return this.sendControl({type:`pong`,time_sent:n.time_sent});case`drain`:return this.end({type:`retry`,error:`server draining: ${n.reason}`,attached:!0})}}onData(e){let t=Vt(e);t&&this.channels.get(t.conn)?.push(t.payload)}open(e,t,n){let r=Gt(t,this.options.identity.hostname),i=r===void 0?void 0:this.hooks.target(r),a=i===void 0?void 0:Kt(i);if(r===void 0||!a){this.sendControl({type:`reset`,conn:e,code:J.UNKNOWN_ROUTE});return}let o=new tn(e,r,a,this.secureContext(),this);this.channels.set(e,o),this.hooks.connections(this.channels.size),this.hooks.emit({type:`connection-opened`,conn:e,route:r,peer:n})}closed(e,t){this.channels.get(e.conn)===e&&(this.channels.delete(e.conn),this.hooks.connections(this.channels.size),this.hooks.emit({type:`connection-closed`,conn:e.conn,route:e.route,...t===void 0?{}:{error:t}}))}sendControl(e){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify(e))}sendData(e,t,n){let r=this.socket;if(!this.finish||r?.readyState!==WebSocket.OPEN){n(/* @__PURE__ */ Error(`bridge closed`));return}for(let n=0;n<t.byteLength;n+=Y.MAX_PAYLOAD_SIZE){let i=t.subarray(n,n+Y.MAX_PAYLOAD_SIZE);r.send(Ht(e,i))}if(r.bufferedAmount<1048576){n();return}this.drainWaiters.push(()=>n()),this.drainTimer??=setInterval(()=>{if(!(this.socket&&this.socket.bufferedAmount>262144&&this.finish)){clearInterval(this.drainTimer),this.drainTimer=void 0;for(let e of this.drainWaiters.splice(0))e()}},5)}},tn=class{conn;route;session;raw;tls;upstream;done=!1;constructor(e,t,n,r,i){this.conn=e,this.route=t,this.session=i,this.raw=new Te({allowHalfOpen:!0,read(){},write:(t,n,r)=>i.sendData(e,t,r),final:t=>{i.sendControl({type:`end`,conn:e}),t()}}),this.tls=new Ee.TLSSocket(this.raw,{isServer:!0,secureContext:r}),this.tls.on(`secure`,()=>{let e=we.connect({host:n.host,port:n.port,allowHalfOpen:!0});this.upstream=e,e.setNoDelay(!0),e.on(`error`,t=>this.destroy(e.connecting?J.UPSTREAM_CONNECT_FAILED:J.UPSTREAM_IO_ERROR,!0,t.message)),this.tls.pipe(e),e.pipe(this.tls)}),this.tls.on(`error`,e=>this.destroy(J.UPSTREAM_IO_ERROR,!0,e.message)),this.tls.on(`close`,()=>this.finish(void 0))}push(e){if(!this.done){if(this.raw.readableLength>8388608){this.destroy(J.UPSTREAM_IO_ERROR,!0,`local connection stalled`);return}this.raw.push(e)}}endInbound(){this.done||this.raw.push(null)}destroy(e,t=!0,n=e){this.done||(t&&this.session.sendControl({type:`reset`,conn:this.conn,code:e}),this.finish(n))}finish(e){this.done||(this.done=!0,this.upstream?.destroy(),this.tls.destroy(),this.raw.destroy(),this.session.closed(this,e))}};const X=e=>e?.profile??`default`,Z=(e,t)=>new wt({message:e,cause:t}),nn=e=>{let t=new Uint8Array(e),n=``;for(let e of t)n+=String.fromCharCode(e);let r=btoa(n);return`-----BEGIN PRIVATE KEY-----\n${r.match(/.{1,64}/g)?.join(`
|
|
5
|
+
`)??r}\n-----END PRIVATE KEY-----\n`};var rn=class e extends me.Service()(`@opentunnel/client/OpenTunnelClient`){static layer(t={}){let n=t.storage??Tt();return D.effect(e,E.gen(function*(){let e=yield*_t,r=new URL(t.api??`https://opentunnel.xyz`),i=E.fn(`OpenTunnelClient.tunnel.get`)(function*(e){return yield*n.load(X(e))}),a=E.fn(`OpenTunnelClient.tunnel.completePending`)(function*(t){let r=yield*e.authorized(B.make(t.pending.token));yield*E.sync(()=>t.onProgress?.(`requesting-certificate`)),yield*r.tunnel[`tunnel.bindCertificate`]({params:{id:z.make(t.pending.id)},payload:{csr:t.pending.csr}}).pipe(E.catchTag(`CertificateInProgressError`,()=>E.void),E.mapError(e=>Z(`Failed to start certificate issuance`,e)));let i=yield*E.gen(function*(){for(;;){yield*E.sync(()=>t.onProgress?.(`waiting-certificate`));let e=yield*r.tunnel[`tunnel.getCertificate`]({params:{id:z.make(t.pending.id)}}).pipe(E.mapError(e=>Z(`Failed to read certificate`,e)));if(e.state.type===`ready`)return e.state;if(e.state.type===`failed`)return yield*new wt({message:`Certificate issuance failed: ${e.state.reason}`});yield*E.sleep(`2 seconds`)}}),a={id:t.pending.id,hostname:t.pending.hostname,token:t.pending.token,privateKey:t.pending.privateKey,certificate:i.certificate,chain:i.chain,certificateExpiry:new Date(i.expiry)};return yield*E.sync(()=>t.onProgress?.(`saving-identity`)),yield*n.save(t.profile,a),yield*E.sync(()=>t.onProgress?.(`ready`)),a}),o=E.fn(`OpenTunnelClient.tunnel.provision`)(function*(e){yield*E.sync(()=>e.onProgress?.(`generating-key`));let t=yield*E.tryPromise({try:()=>crypto.subtle.generateKey({name:`ECDSA`,namedCurve:`P-256`},!0,[`sign`,`verify`]),catch:e=>Z(`Failed to generate certificate key`,e)});yield*E.sync(()=>e.onProgress?.(`generating-csr`));let r=yield*E.tryPromise({try:()=>Ct(e.hostname,t),catch:e=>Z(`Failed to generate certificate request`,e)}),i=yield*E.tryPromise({try:()=>crypto.subtle.exportKey(`pkcs8`,t.privateKey),catch:e=>Z(`Failed to export certificate key`,e)}),o={id:String(e.id),hostname:e.hostname,token:e.token,privateKey:nn(i),csr:r};return yield*n.savePending(e.profile,o),yield*a({profile:e.profile,pending:o,onProgress:e.onProgress})}),s=E.fn(`OpenTunnelClient.tunnel.create`)(function*(t){let r=X(t);if(yield*n.load(r))return yield*new wt({message:`Profile '${r}' already has a tunnel`});let i=yield*n.loadPending(r);if(i)return yield*E.sync(()=>t?.onProgress?.(`resuming-certificate`)),yield*a({profile:r,pending:i,onProgress:t?.onProgress});yield*E.sync(()=>t?.onProgress?.(`creating-tunnel`));let s=yield*e.client.tunnel[`tunnel.create`]({payload:{}}).pipe(E.mapError(e=>Z(`Failed to create tunnel`,e)));return yield*o({profile:r,id:s.tunnel.id,hostname:String(s.tunnel.hostname),token:s.token,onProgress:t?.onProgress})}),c=E.fn(`OpenTunnelClient.tunnel.ensure`)(function*(t){let r=yield*i(t);if(!r)return yield*s(t);let a=(yield*(yield*e.authorized(B.make(r.token))).tunnel[`tunnel.getCertificate`]({params:{id:z.make(r.id)}}).pipe(E.mapError(e=>Z(`Failed to read certificate`,e)))).state;if(a.type===`ready`&&a.certificate!==r.certificate){let e={...r,certificate:a.certificate,chain:a.chain,certificateExpiry:new Date(a.expiry)};return yield*n.save(X(t),e),e}return a.type===`failed`?yield*o({profile:X(t),id:z.make(r.id),hostname:r.hostname,token:B.make(r.token)}):r}),l=E.fn(`OpenTunnelClient.tunnel.pending`)(function*(e){let t=yield*n.loadPending(X(e));return t?{id:t.id,hostname:t.hostname}:void 0}),u=E.fn(`OpenTunnelClient.tunnel.resume`)(function*(e){let t=X(e),r=yield*n.loadPending(t);if(r)return yield*E.sync(()=>e?.onProgress?.(`resuming-certificate`)),yield*a({profile:t,pending:r,onProgress:e?.onProgress})});return{profile:{list:n.profiles},tunnel:{list:n.list,get:i,pending:l,resume:u,create:s,ensure:c,remove:E.fn(`OpenTunnelClient.tunnel.remove`)(function*(t){let r=X(t),i=yield*n.load(r);i&&(yield*(yield*e.authorized(B.make(i.token))).tunnel[`tunnel.remove`]({params:{id:z.make(i.id)}}).pipe(E.mapError(e=>Z(`Failed to remove tunnel`,e))),yield*n.remove(r))}),connect:E.fn(`OpenTunnelClient.tunnel.connect`)(function*(e){yield*E.try({try:()=>Yt(e.routes),catch:e=>Z(e instanceof Error?e.message:`Invalid routes`,e)});let t=yield*c(e),i=yield*O.unbounded(),a=yield*E.acquireRelease(E.sync(()=>new $t({api:r,identity:t,routes:e.routes,onEvent:e=>{O.offerUnsafe(i,e),e.type===`stopped`&&O.endUnsafe(i)},onRenewed:t=>{E.runFork(n.save(X(e),t).pipe(E.ignore))}})),e=>E.promise(()=>e.close()).pipe(E.andThen(O.end(i))));return yield*E.tryPromise({try:()=>a.ready,catch:e=>Z(`Failed to connect tunnel`,e)}),{tunnel:t,events:ge.fromQueue(i),status:E.sync(()=>a.getStatus()),setRoutes:e=>E.try({try:()=>a.setRoutes(e),catch:e=>Z(e instanceof Error?e.message:`Invalid routes`,e)}),closed:E.tryPromise({try:()=>a.closed,catch:e=>Z(`Tunnel stopped`,e)}),close:E.promise(()=>a.close())}})}}})).pipe(D.provide(_t.layer({api:t.api??`https://opentunnel.xyz`})))}};const an=r.Union([r.Struct({jsonrpc:r.Literal(`2.0`),id:P.RequestId,result:r.Unknown}),r.Struct({jsonrpc:r.Literal(`2.0`),id:r.NullOr(P.RequestId),error:P.McpErrorBase}),r.Struct({jsonrpc:r.Literal(`2.0`),method:r.String,params:r.optionalKey(r.Unknown)})]),on=r.fromJsonString(an),sn=N.v2025_06_18.protocolVersion;var Q=class extends r.TaggedError()(`OrbitMcpError`,{message:r.String}){},cn=class extends l.Service()(`@2digits/tlo-mcp/mcp/OrbitClient`){};const ln=p.effect(cn,n.gen(function*(){let e=yield*R,t=(yield*_.HttpClient).pipe(_.withScope,_.transformResponse(n.flatMap(te.matchStatus({"2xx":e=>n.succeed(e),401:e=>Q.make({message:`Orbit HTTP ${e.status}: authorization rejected. Run tlo-mcp login; a 403 can also mean insufficient rights.`}),403:e=>Q.make({message:`Orbit HTTP ${e.status}: authorization rejected. Run tlo-mcp login; a 403 can also mean insufficient rights.`}),429:e=>Q.make({message:`Orbit rate limited. Retry after ${e.headers[`retry-after`]??`the server cooldown`}; request not replayed.`}),orElse:e=>Q.make({message:`Orbit HTTP ${e.status}. Request not replayed.`})})))),i=0,a,o=n.fn(`OrbitClient.send`)(function*(o,s,c=!1){let l=++i,u=yield*e.accessToken,d=yield*v.post(F).pipe(v.bearerToken(u),v.setHeader(`accept`,`application/json, text/event-stream`),v.setHeader(`mcp-protocol-version`,sn),e=>a===void 0?e:v.setHeader(e,`mcp-session-id`,a),v.bodyJson({jsonrpc:`2.0`,...c?{}:{id:l},method:o,params:s})),f=yield*t.execute(d),p=f.headers[`mcp-session-id`];if(o===`initialize`&&(a=p),c)return;let m=f.headers[`content-type`]?.startsWith(`text/event-stream`)?yield*f.stream.pipe(T.decodeText(),T.pipeThroughChannel(je.decode({maxEventSize:10485760})),T.filter(e=>e.event===`message`||e.event===``),T.map(Ae.get(`data`)),T.mapEffect(e=>r.decodeEffect(on)(e)),T.filter(e=>`id`in e&&e.id===l),T.runHead,n.flatMap(e=>ke.isSome(e)?n.succeed(e.value):Q.make({message:`Orbit stream ended before replying.`}))):yield*te.schemaBodyJson(an)(f);return!(`id`in m)||m.id!==l?yield*Q.make({message:`Unexpected Orbit response ID.`}):`error`in m?yield*Q.make({message:m.error.message}):m.result},n.timeout(`120 seconds`),n.scoped,n.provideService(g.RequestInit,{redirect:`error`}),n.mapError(e=>r.is(Q)(e)?e:Q.make({message:`Orbit connection or response failed. Request not replayed.`}))),s=yield*o(`initialize`,{protocolVersion:sn,capabilities:{},clientInfo:{name:`@2digits/tlo-mcp`,version:`0.1.38`}}).pipe(n.flatMap(r.decodeUnknownEffect(P.InitializeResult)));if(s.protocolVersion!==sn)return yield*Q.make({message:`Unsupported upstream MCP protocol: ${s.protocolVersion}.`});yield*o(`notifications/initialized`,{},!0);let c=/* @__PURE__ */ new Set,l=yield*T.paginate(void 0,n.fn(`OrbitClient.listToolsPage`)(function*(e){let t=yield*o(`tools/list`,e===void 0?{}:{cursor:e}).pipe(n.flatMap(r.decodeUnknownEffect(P.ListToolsResult)));if(t.nextCursor!==void 0){if(c.has(t.nextCursor))return yield*Q.make({message:`Orbit returned a repeated tools cursor.`});c.add(t.nextCursor)}return[t.tools,ke.fromUndefinedOr(t.nextCursor)]})).pipe(T.runCollect);return cn.of({tools:l,instructions:s.instructions,callTool:n.fn(`OrbitClient.callTool`)(function*(e,t){return yield*o(`tools/call`,{name:e,arguments:t})},n.flatMap(r.decodeUnknownEffect(P.CallToolResult)),n.catchTag(`SchemaError`,()=>Q.make({message:`Invalid Orbit tool result. Request not replayed.`})))})})).pipe(p.provide(g.layer)),un=p.effectDiscard(n.gen(function*(){let e=yield*cn,t=yield*Oe.McpServer;yield*n.forEach(e.tools,n.fn(`OrbitProxyLive.tool`)(function*(i){yield*t.addTool({tool:i,annotations:l.empty(),handle:n.fn(`handle`)(function*(e){return yield*r.decodeUnknownEffect(r.JsonObject)(e)},n.mapError(e=>P.InvalidParams.make({message:`Tool arguments must be a JSON object.`,data:e})),n.flatMap(t=>e.callTool(i.name,t).pipe(n.mapError(e=>P.InternalError.make({message:e.message})))))})}),{discard:!0})})),dn=`${de()}/.config/2digits/tlo-mcp/gateway.key`,fn=r.String.check(r.isPattern(/^[a-f0-9]{64}$/)).pipe(r.RedactedFromValue);var $=class extends r.TaggedError()(`GatewayError`,{message:r.String}){};const pn=n.fn(`Gateway.loadKey`)(function*(e){let t=yield*fe.FileSystem,i=(yield*pe.Path).dirname(e);yield*t.makeDirectory(i,{recursive:!0,mode:448}),yield*t.chmod(i,448),yield*t.writeFileString(e,ae(32).toString(`hex`),{flag:`wx`,mode:384}).pipe(n.catchReason(`PlatformError`,`AlreadyExists`,()=>n.void));let a=yield*t.stat(e);return a.type!==`File`||a.mode&63?yield*$.make({message:`Gateway key must be a private file (chmod 600).`}):yield*t.readFileString(e).pipe(n.flatMap(r.decodeEffect(fn)))},n.mapError(()=>$.make({message:`Cannot load private gateway key. Check its permissions and format.`})));function mn(e){let t=ie(`sha256`).update(`Bearer ${h.value(e)}`).digest();return y.middleware(e=>n.gen(function*(){let r=yield*b.HttpServerRequest,i=ie(`sha256`).update(r.headers.authorization??``).digest();return oe(t,i)?r.headers.origin===void 0?yield*e.pipe(n.provideService(ne.MaxBodySize,De.bytes(1048576)),n.map(e=>x.setHeader(e,`cache-control`,`no-store`))):x.empty({status:403,headers:{"cache-control":`no-store`}}):x.empty({status:401,headers:{"www-authenticate":`Bearer`,"cache-control":`no-store`}})}),{global:!0})}function hn(e){let t=p.unwrap(n.gen(function*(){let e=yield*cn;return un.pipe(p.provide(Oe.layerHttp({name:`tlo-mcp`,version:`1.0.0`,instructions:e.instructions,path:`/mcp`,protocols:[N.v2025_11_25,N.v2025_06_18,N.v2025_03_26]})))}));return p.merge(t,mn(e))}const gn=r.String.check(r.isPattern(/^[a-z0-9]+\.opentunnel\.xyz$/)),_n=n.fn(`Gateway.openTunnel`)(function*(e){let t=yield*(yield*rn).tunnel.connect({profile:`tlo-mcp`,routes:{tlo:`127.0.0.1:${e}`}}),i=yield*r.decodeEffect(gn)(t.tunnel.hostname);return yield*t.events.pipe(T.runForEach(e=>e.type===`reconnecting`?n.sync(()=>process.stderr.write(`OpenTunnel reconnecting...
|
|
6
|
+
`)):n.void),n.forkScoped),{endpoint:`https://tlo.${i}/mcp`,closed:t.closed.pipe(n.mapError(()=>$.make({message:`OpenTunnel stopped unexpectedly. Restart tlo-mcp.`})))}},n.timeout(`180 seconds`),n.mapError(()=>$.make({message:`Could not connect OpenTunnel. Check network access and retry; existing tunnel identity is preserved.`})));function vn(e){return`Connect my personal Teamleader Orbit MCP to Executor Cloud.
|
|
7
|
+
Endpoint: ${e}
|
|
8
|
+
Transport: Streamable HTTP. Authentication: API key in Authorization: Bearer <key>.
|
|
9
|
+
Register an MCP integration named Teamleader Orbit (slug tlo_mcp) with a bearer-header API-key template, not OAuth.
|
|
10
|
+
Use a secure credential handoff so I enter the gateway key directly in Executor; never ask me to paste it into chat.
|
|
11
|
+
Create a personal connection, discover tools, and verify with one read-only tool. Do not change business data.
|
|
12
|
+
If this endpoint already exists, reuse its integration/connection instead of creating duplicates.
|
|
13
|
+
Orbit login and refresh are handled on my Mac; do not register another Orbit OAuth client.
|
|
14
|
+
`}const yn=n.fn(`TloMcp.connect`)(function*(e){let t=yield*pn(dn),r=yield*L,i=yield*R;(yield*r.load)===void 0&&(yield*i.login),yield*p.build(y.serve(hn(t),{disableLogger:!0,disableListenLog:!0}).pipe(p.provide(ln),p.provide(ce.layer(()=>se({maxHeaderSize:8192,requestTimeout:13e4}),{host:`127.0.0.1`,port:e})))),yield*n.sync(()=>process.stderr.write(`Connecting OpenTunnel (first certificate may take a few minutes)...
|
|
15
|
+
`));let{endpoint:a,closed:o}=yield*_n(e);return yield*n.sync(()=>{process.stderr.write(`TLO gateway ready: ${a}\nGateway key: ${dn} (private; not printed)\n`),process.platform===`darwin`&&process.stderr.write(`Copy the key for Executor's secure credential form: pbcopy < "${dn}"\n`),process.stderr.write(`Keep this process running and your Mac awake. Ctrl-C closes the gateway and tunnel.
|
|
16
|
+
|
|
17
|
+
Copy this prompt to your agent:
|
|
18
|
+
|
|
19
|
+
`),process.stdout.write(vn(a))}),yield*o,yield*$.make({message:`The OpenTunnel connection stopped. Restart tlo-mcp.`})},n.scoped,n.provide(p.mergeAll(Ke,We,rn.layer()))),bn=i.make(`login`,{},n.fn(`TloMcp.login`)(function*(){yield*(yield*R).login},n.provide(Ke),n.scoped)),xn=i.make(`logout`,{},n.fn(`TloMcp.logout`)(function*(){yield*(yield*R).logout},n.provide(Ke),n.scoped)),Sn=i.make(`tlo-mcp`,{port:a.Int(`port`).pipe(a.withSchema(r.Int.check(r.isBetween({minimum:1024,maximum:65535}))),a.withDefault(4790))},({port:e})=>yn(e)).pipe(i.withSubcommands([bn,xn]));n.fn(`TloMcp.run`)(function*(e){return yield*i.runWith(Sn,{version:`1.0.0`})(e)})(process.argv.slice(2)).pipe(n.provide(t.layer),e.runMain);export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2digits/tlo-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/2digits-agency/configs.git",
|
|
@@ -14,32 +14,32 @@
|
|
|
14
14
|
],
|
|
15
15
|
"type": "module",
|
|
16
16
|
"sideEffects": false,
|
|
17
|
-
"types": "./dist/index.d.mts",
|
|
18
|
-
"exports": {
|
|
19
|
-
".": "./dist/index.mjs",
|
|
20
|
-
"./bin": "./dist/bin.mjs",
|
|
21
|
-
"./package.json": "./package.json"
|
|
22
|
-
},
|
|
23
17
|
"publishConfig": {
|
|
24
18
|
"access": "public"
|
|
25
19
|
},
|
|
26
20
|
"dependencies": {
|
|
27
|
-
"@effect/platform-node": "4.0.
|
|
28
|
-
"
|
|
21
|
+
"@effect/platform-node": "4.0.2",
|
|
22
|
+
"@opentunnel/client": "0.2.2",
|
|
23
|
+
"@yielded/oauth": "0.1.0-beta.30",
|
|
24
|
+
"effect": "4.0.2"
|
|
29
25
|
},
|
|
30
26
|
"devDependencies": {
|
|
31
|
-
"@2digits/tsconfig": "0.9.
|
|
27
|
+
"@2digits/tsconfig": "0.9.2",
|
|
32
28
|
"@arethetypeswrong/core": "0.18.5",
|
|
33
|
-
"@effect/vitest": "4.0.
|
|
29
|
+
"@effect/vitest": "4.0.2",
|
|
34
30
|
"publint": "0.3.25",
|
|
35
31
|
"typescript": "7.0.2",
|
|
36
|
-
"vite-plus": "1.
|
|
32
|
+
"vite-plus": "1.1.0",
|
|
37
33
|
"vitest": "5.0.3"
|
|
38
34
|
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=24"
|
|
37
|
+
},
|
|
39
38
|
"public": true,
|
|
40
39
|
"scripts": {
|
|
41
40
|
"build": "vp pack --minify --config-loader unconfig",
|
|
42
41
|
"dev": "vp pack --watch --config-loader unconfig",
|
|
42
|
+
"run": "vp run build && node ./dist/bin.mjs",
|
|
43
43
|
"test": "vitest run",
|
|
44
44
|
"test:watch": "vitest",
|
|
45
45
|
"types": "tsc --noEmit"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import*as e from"effect/Context";import*as t from"effect/Effect";import*as n from"effect/Layer";import*as r from"effect/Match";import*as i from"effect/Option";import*as a from"effect/Record";import*as ee from"effect/Redacted";import*as o from"effect/Schema";import*as s from"effect/http/HttpBody";import*as te from"effect/http/UrlParams";import*as c from"effect/http/Cookies";import*as l from"effect/http/HttpClient";import*as u from"effect/http/HttpClientRequest";import*as d from"effect/Array";import*as f from"effect/String";import*as p from"effect/Config";import*as m from"effect/http/FetchHttpClient";var h=class extends e.Service()(`@2digits/tlo-mcp/services/TloConfig`){},g=class extends o.TaggedError()(`TloApiError`,{message:o.String,code:o.optional(o.String),endpoint:o.optional(o.String)}){},_=class extends o.TaggedError()(`TloAuthError`,{message:o.String}){},v=class extends o.TaggedError()(`TloNetworkError`,{message:o.String,cause:o.optional(o.Unknown),endpoint:o.optional(o.String)}){},y=class extends o.TaggedError()(`TloParseError`,{message:o.String,cause:o.optional(o.Unknown)}){};const ne=o.Union([g,_,v,y]);var b=class extends e.Service()(`@2digits/tlo-mcp/services/TloHttpClient`){};const x=n.effect(b,t.gen(function*(){let e=yield*h,t=yield*l.HttpClient,n=c.toCookieHeader(e.cookies),r=t.pipe(l.mapRequest(u.prependUrl(e.baseUrl)),l.mapRequest(u.setHeader(`Cookie`,n)),l.filterStatusOk);return b.of({client:r})}));var S=class extends e.Service()(`@2digits/tlo-mcp/services/TeamLeaderClient`){};const re=o.Struct({MSG:o.String,err:o.Finite}),ie=o.fromJsonString(o.Unknown),ae=/\{MSG:'([^']*)',\s*err:(\d+)\}/;function oe(e){let t=ae.exec(e);return r.value(t).pipe(r.when(r.defined,e=>i.some({MSG:e[1]??`Unknown error`,err:Number(e[2])})),r.orElse(()=>i.none()))}const C=n.effect(S,t.gen(function*(){let e=yield*h,{client:n}=yield*b;return S.of({post:t.fn(`TeamLeaderClient.post`)(function*(i,c,l){let u=a.set(c,`t`,ee.value(e.sessionToken)),d=te.fromInput(u),f=yield*(yield*n.post(i,{body:s.urlParams(d)})).text,p=oe(f);yield*r.value(p).pipe(r.tag(`Some`,({value:e})=>e.err===0?t.void:g.make({message:e.MSG,endpoint:i})),r.tag(`None`,()=>t.void),r.exhaustive);let m=yield*o.decodeEffect(ie)(f).pipe(t.mapError(e=>y.make({message:`Invalid JSON response`,cause:e})));return yield*r.value(m).pipe(r.when(o.is(re),e=>e.err===0?t.void:g.make({message:e.MSG,endpoint:i})),r.orElse(()=>t.void)),yield*o.decodeUnknownEffect(l)(m).pipe(t.mapError(e=>y.make({message:`Failed to parse response`,cause:e})))},t.scoped,(e,n)=>e.pipe(t.mapError(e=>r.value(e).pipe(r.when(o.is(g),e=>e),r.when(o.is(y),e=>e),r.orElse(e=>v.make({message:e.response===void 0?`Request failed: ${e.message}`:`HTTP ${e.response.status}`,cause:e,endpoint:n}))))))})})),w=o.Struct({ID:o.Finite,FOLDERID:o.optional(o.Union([o.String,o.Finite])),PROJECT_ID:o.optional(o.Union([o.String,o.Finite])),TASKID:o.Union([o.String,o.Finite]).pipe(o.NullOr,o.optional),TODOID:o.Union([o.String,o.Finite]).pipe(o.NullOr,o.optional),TICKETID:o.Union([o.String,o.Finite]).pipe(o.NullOr,o.optional),CONTACTID:o.optional(o.Union([o.String,o.Finite])),DESCRIPTION:o.NullOr(o.String),DT:o.String,START_DT:o.optional(o.String),END_DT:o.optional(o.String),DURATION:o.Finite,TYPE:o.optional(o.Finite),PROJECT_NAME:o.String.pipe(o.NullOr,o.optional),TASK_NAME:o.String.pipe(o.NullOr,o.optional),CLIENT_NAME:o.String.pipe(o.NullOr,o.optional),FOLDER_NAME:o.String.pipe(o.NullOr,o.optional),ENTITY_NAME:o.String.pipe(o.NullOr,o.optional),PROJECTOWNER_NAME:o.String.pipe(o.NullOr,o.optional),CLIENT_COLOR:o.String.pipe(o.NullOr,o.optional),PROJECT_COLOR:o.String.pipe(o.NullOr,o.optional),PROJECT_STATE:o.String.pipe(o.NullOr,o.optional),TASK_STATE:o.String.pipe(o.NullOr,o.optional),TIMESHEET_STATE:o.String.pipe(o.NullOr,o.optional),BILLING_MODE:o.String.pipe(o.NullOr,o.optional),NON_BILLABLE:o.Finite.pipe(o.NullOr,o.optional),RATE:o.Finite.pipe(o.NullOr,o.optional),VALUE:o.Finite.pipe(o.NullOr,o.optional),PROJECT_START_DT:o.String.pipe(o.NullOr,o.optional),PROJECT_END_DT:o.String.pipe(o.NullOr,o.optional),TASK_START_DT:o.String.pipe(o.NullOr,o.optional),TASK_END_DT:o.String.pipe(o.NullOr,o.optional),READONLY:o.Finite.pipe(o.NullOr,o.optional),TASK_WORKLOAD:o.Finite.pipe(o.NullOr,o.optional)});var T=class extends o.Class(`Activity`)({id:o.Finite,projectId:o.optional(o.String),folderId:o.optional(o.String),taskId:o.String.pipe(o.NullOr,o.optional),todoId:o.String.pipe(o.NullOr,o.optional),ticketId:o.String.pipe(o.NullOr,o.optional),userId:o.optional(o.String),description:o.NullOr(o.String),startDate:o.String,endDate:o.optional(o.String),durationMinutes:o.Finite,projectName:o.String.pipe(o.NullOr,o.optional),taskName:o.String.pipe(o.NullOr,o.optional),clientName:o.String.pipe(o.NullOr,o.optional),folderName:o.String.pipe(o.NullOr,o.optional),entityName:o.String.pipe(o.NullOr,o.optional),projectOwnerName:o.String.pipe(o.NullOr,o.optional),projectState:o.String.pipe(o.NullOr,o.optional),taskState:o.String.pipe(o.NullOr,o.optional),timesheetState:o.String.pipe(o.NullOr,o.optional),billingMode:o.String.pipe(o.NullOr,o.optional),nonBillable:o.Finite.pipe(o.NullOr,o.optional),rate:o.Finite.pipe(o.NullOr,o.optional),value:o.Finite.pipe(o.NullOr,o.optional),projectStartDate:o.String.pipe(o.NullOr,o.optional),projectEndDate:o.String.pipe(o.NullOr,o.optional),taskStartDate:o.String.pipe(o.NullOr,o.optional),taskEndDate:o.String.pipe(o.NullOr,o.optional),readOnly:o.Boolean.pipe(o.NullOr,o.optional),taskWorkload:o.Finite.pipe(o.NullOr,o.optional),projectColor:o.optional(o.String),clientColor:o.optional(o.String)}){};function E(e){return T.make({id:e.ID,projectId:e.PROJECT_ID===void 0?e.FOLDERID===void 0?void 0:String(e.FOLDERID):String(e.PROJECT_ID),folderId:e.FOLDERID===void 0?void 0:String(e.FOLDERID),taskId:e.TASKID!==void 0&&e.TASKID!==null?String(e.TASKID):null,todoId:e.TODOID!==void 0&&e.TODOID!==null?String(e.TODOID):null,ticketId:e.TICKETID!==void 0&&e.TICKETID!==null?String(e.TICKETID):null,userId:e.CONTACTID===void 0?void 0:String(e.CONTACTID),description:e.DESCRIPTION,startDate:e.DT,endDate:e.END_DT,durationMinutes:e.DURATION,projectName:e.PROJECT_NAME,taskName:e.TASK_NAME,clientName:e.CLIENT_NAME,folderName:e.FOLDER_NAME,entityName:e.ENTITY_NAME,projectOwnerName:e.PROJECTOWNER_NAME,projectState:e.PROJECT_STATE,taskState:e.TASK_STATE,timesheetState:e.TIMESHEET_STATE,billingMode:e.BILLING_MODE,nonBillable:e.NON_BILLABLE,rate:e.RATE,value:e.VALUE,projectStartDate:e.PROJECT_START_DT,projectEndDate:e.PROJECT_END_DT,taskStartDate:e.TASK_START_DT,taskEndDate:e.TASK_END_DT,readOnly:e.READONLY===void 0||e.READONLY===null?void 0:e.READONLY!==0,taskWorkload:e.TASK_WORKLOAD,projectColor:e.PROJECT_COLOR??void 0,clientColor:e.CLIENT_COLOR??void 0})}const se=o.Struct({ACTIVITIES:o.Array(w)}),D=o.Struct({ID:o.Finite,DURATION:o.optional(o.Finite),DT:o.optional(o.String),TYPE:o.optional(o.Finite),DESCRIPTION:o.String.pipe(o.NullOr,o.optional),CLIENT_COLOR:o.optional(o.String),FOLDERID:o.optional(o.Union([o.String,o.Finite]))});function O(e){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}T${String(e.getHours()).padStart(2,`0`)}:${String(e.getMinutes()).padStart(2,`0`)}:${String(e.getSeconds()).padStart(2,`0`)}`}var k=class extends e.Service()(`@2digits/tlo-mcp/services/TimeService`){};const A=n.effect(k,t.gen(function*(){let e=yield*S;return k.of({getWeek:t.fn(`TimeService.getWeek`)(function*(t,n,r=`Europe/Amsterdam`){let i=yield*e.post(`/ajax/pln/GetWeek`,{DT:O(t),CONTACTID:n,tmz:r},se);return d.map(E)(i.ACTIVITIES)}),createActivity:t.fn(`TimeService.createActivity`)(function*(t){return(yield*e.post(`/ajax/pln/SetActivity`,{ID:0,ACTION:`CREATE`,DT:O(t.startDate),DURATION:t.durationMinutes,FOLDERID:t.folderId,TASKID:t.taskId,TODOID:t.todoId,CONTACTID:t.contactId,CLIENT_COLOR:t.clientColor,DESCRIPTION:t.description},D)).ID}),updateActivity:t.fn(`TimeService.updateActivity`)(function*(t){yield*e.post(`/ajax/pln/SetActivity`,{ID:t.id,ACTION:`MOVE`,DT:t.startDate===void 0?void 0:O(t.startDate),DURATION:t.durationMinutes,DESCRIPTION:t.description},D)}),deleteActivity:t.fn(`TimeService.deleteActivity`)(function*(t){yield*e.post(`/ajax/pln/SetActivity`,{ID:t.id,ACTION:`DELETE`},D)})})})),j=o.Struct({ID:o.Finite,PROJECT_NAME:o.optional(o.String),NAME:o.optional(o.String),STATE:o.optional(o.String),OWNER_NAME:o.optional(o.String),START_DT:o.optional(o.String),END_DT:o.optional(o.String),BILLING_BUDGET:o.optional(o.Finite),BILLING_MODE:o.optional(o.String),BILLING_REMAINING:o.optional(o.Finite),CLIENT_NAME:o.optional(o.String),CLIENT_ID:o.optional(o.Finite),FOLDERID:o.optional(o.Finite),FOLDER_NAME:o.optional(o.String),EXTID:o.optional(o.String),CALC_TOTAL:o.optional(o.Finite),CALC_DONE:o.optional(o.Finite),CALC_PLANNED:o.optional(o.Finite)}),ce=o.Struct({Records:o.Array(j),RecordCount:o.optional(o.Finite)}),le=o.Union([j,o.Struct({Record:j})]);var M=class extends o.Class(`Project`)({id:o.Finite,name:o.String,state:o.optional(o.String),ownerName:o.optional(o.String),startDate:o.optional(o.String),endDate:o.optional(o.String),billingBudget:o.optional(o.Finite),billingMode:o.optional(o.String),billingRemaining:o.optional(o.Finite),clientName:o.optional(o.String),clientId:o.optional(o.Finite),folderId:o.optional(o.Finite),folderName:o.optional(o.String),extId:o.optional(o.String),calcTotal:o.optional(o.Finite),calcDone:o.optional(o.Finite),calcPlanned:o.optional(o.Finite)}){};function N(e){return M.make({id:e.ID,name:e.PROJECT_NAME??e.NAME??``,state:e.STATE,ownerName:e.OWNER_NAME,startDate:e.START_DT,endDate:e.END_DT,billingBudget:e.BILLING_BUDGET,billingMode:e.BILLING_MODE,billingRemaining:e.BILLING_REMAINING,clientName:e.CLIENT_NAME,clientId:e.CLIENT_ID,folderId:e.FOLDERID,folderName:e.FOLDER_NAME,extId:e.EXTID,calcTotal:e.CALC_TOTAL,calcDone:e.CALC_DONE,calcPlanned:e.CALC_PLANNED})}const P=o.Struct({ID:o.Finite,OWNERID:o.optional(o.Finite),OWNER_NAME:o.optional(o.String),AVATAR:o.optional(o.String),CREATED_DT:o.optional(o.String),CONTENT:o.optional(o.String),SUBCONTENT:o.optional(o.String),OBJECTTYPE:o.optional(o.String),OBJECTID:o.optional(o.Finite),OBJECTNAME:o.optional(o.String),MSGTYPE:o.optional(o.String),LIKECNT:o.optional(o.Finite),REPLYCNT:o.optional(o.Finite),OUTQUEUEID:o.optional(o.String),INTERNAL:o.optional(o.Finite)}),F=o.Array(P);var I=class extends o.Class(`Message`)({id:o.Finite,ownerId:o.optional(o.Finite),ownerName:o.optional(o.String),avatar:o.optional(o.String),createdAt:o.optional(o.String),content:o.optional(o.String),subcontent:o.optional(o.String),objectType:o.optional(o.String),objectId:o.optional(o.Finite),objectName:o.optional(o.String),messageType:o.optional(o.String),likeCount:o.optional(o.Finite),replyCount:o.optional(o.Finite),outQueueId:o.optional(o.String),internal:o.optional(o.Boolean)}){};function L(e){return I.make({id:e.ID,ownerId:e.OWNERID,ownerName:e.OWNER_NAME,avatar:e.AVATAR,createdAt:e.CREATED_DT,content:e.CONTENT,subcontent:e.SUBCONTENT,objectType:e.OBJECTTYPE,objectId:e.OBJECTID,objectName:e.OBJECTNAME,messageType:e.MSGTYPE,likeCount:e.LIKECNT,replyCount:e.REPLYCNT,outQueueId:e.OUTQUEUEID,internal:e.INTERNAL===void 0?void 0:e.INTERNAL!==0})}const R=o.Struct({ID:o.Finite,PROJECTID:o.optional(o.Finite),NAME:o.optional(o.String),STATE:o.optional(o.String),OWNER_NAME:o.optional(o.String),START_DT:o.optional(o.String),END_DT:o.optional(o.String),BUDGET:o.optional(o.Finite)}),z=o.Struct({Records:o.Array(R),RecordCount:o.optional(o.Finite)});var B=class extends o.Class(`Task`)({id:o.Finite,projectId:o.optional(o.Finite),name:o.optional(o.String),state:o.optional(o.String),ownerName:o.optional(o.String),startDate:o.optional(o.String),endDate:o.optional(o.String),budget:o.optional(o.Finite)}){};function V(e){return B.make({id:e.ID,projectId:e.PROJECTID,name:e.NAME,state:e.STATE,ownerName:e.OWNER_NAME,startDate:e.START_DT,endDate:e.END_DT,budget:e.BUDGET})}const H=o.Struct({ID:o.Finite,WORKLOAD:o.optional(o.Finite),TOTAL:o.optional(o.Finite),REMAINING:o.optional(o.Finite),AVAIL:o.optional(o.Finite),CLIENTID:o.optional(o.Finite),PROJECTID:o.optional(o.Finite),PROJECT_START_DT:o.optional(o.String),PROJECT_END_DT:o.optional(o.String),START_DT:o.optional(o.String),END_DT:o.optional(o.String),STATE:o.optional(o.String)}),U=o.Struct({Records:o.Array(H)});var W=class extends o.Class(`TaskForUser`)({id:o.Finite,workload:o.optional(o.Finite),total:o.optional(o.Finite),remaining:o.optional(o.Finite),avail:o.optional(o.Finite),clientId:o.optional(o.Finite),projectId:o.optional(o.Finite),projectStartDate:o.optional(o.String),projectEndDate:o.optional(o.String),startDate:o.optional(o.String),endDate:o.optional(o.String),state:o.optional(o.String)}){};function ue(e){return W.make({id:e.ID,workload:e.WORKLOAD,total:e.TOTAL,remaining:e.REMAINING,avail:e.AVAIL,clientId:e.CLIENTID,projectId:e.PROJECTID,projectStartDate:e.PROJECT_START_DT,projectEndDate:e.PROJECT_END_DT,startDate:e.START_DT,endDate:e.END_DT,state:e.STATE})}const G=o.Record(o.String,o.Unknown),de=o.Struct({ID:o.Finite,NAME:o.optional(o.String),BOARDLISTID:o.optional(o.Finite),BOARDID:o.optional(o.Finite),OWNERID:o.optional(o.Finite),EXTID:o.optional(o.String),STATE:o.String.pipe(o.NullOr,o.optional),DUE_DT:o.String.pipe(o.NullOr,o.optional),DESCRIPTION:o.String.pipe(o.NullOr,o.optional)}),K=o.Struct({Records:o.Array(de),RecordCount:o.optional(o.Finite)});var q=class extends o.Class(`TodoSummary`)({id:o.Finite,name:o.optional(o.String),boardListId:o.optional(o.Finite),boardId:o.optional(o.Finite),ownerId:o.optional(o.Finite),extId:o.optional(o.String),state:o.String.pipe(o.NullOr,o.optional),dueDate:o.String.pipe(o.NullOr,o.optional),description:o.String.pipe(o.NullOr,o.optional)}){};function J(e){return q.make({id:e.ID,name:e.NAME,boardListId:e.BOARDLISTID,boardId:e.BOARDID,ownerId:e.OWNERID,extId:e.EXTID,state:e.STATE,dueDate:e.DUE_DT,description:e.DESCRIPTION})}var Y=class extends e.Service()(`@2digits/tlo-mcp/services/BoardService`){};function fe(e){return`${e.getFullYear()}-${(e.getMonth()+1).toString().padStart(2,`0`)}-${e.getDate().toString().padStart(2,`0`)}`}const X=n.effect(Y,t.gen(function*(){let e=yield*S;return Y.of({getProjects:t.fn(`BoardService.getProjects`)(function*(t={}){let n=t.states??[`DRAFT`,`OPEN`,`CLOSED`],r=yield*e.post(`/ajax/pln/GetProjects`,{STATE:d.join(n,`|`),COLUMNS:`ID|PROJECT_NAME|NAME|STATE|OWNER_NAME|START_DT|END_DT|BILLING_BUDGET|BILLING_MODE|BILLING_REMAINING|CLIENT_NAME|CLIENT_ID|FOLDERID|FOLDER_NAME|EXTID|CALC_TOTAL|CALC_DONE|CALC_PLANNED`,limit:t.limit??100,page:t.page??1},ce);return d.map(N)(r.Records)}),getProjectDetails:t.fn(`BoardService.getProjectDetails`)(function*(t){let n=yield*e.post(`/ajax/pln/GetProjectDetails`,{ID:t.projectId},le);return N(r.value(n).pipe(r.when({Record:r.any},({Record:e})=>e),r.orElse(e=>e)))}),getMessages:t.fn(`BoardService.getMessages`)(function*(t){let n=yield*e.post(`/ajax/board/ProcessMessage`,{OBJECTTYPE:t.objectType,OBJECTID:t.objectId,ONLYCOMMENTS:t.onlyComments===!1?0:1,HIDEINTERNAL:t.hideInternal===!1?0:1,PAGETYPE:`SEQID`,MINID:0,limit:t.limit??100},F);return d.map(L)(n)}),getTasks:t.fn(`BoardService.getTasks`)(function*(t){let n=t.states??[`DRAFT`,`OPEN`,`CLOSED`],r=yield*e.post(`/ajax/pln/GetTasks`,{PROJECTID:t.projectId,STATE:d.join(n,`|`),COLUMNS:`ID|PROJECTID|NAME|STATE|OWNER_NAME|START_DT|END_DT|BUDGET`,limit:t.limit??100,page:t.page??1},z);return d.map(V)(r.Records)}),getTasksForUser:t.fn(`BoardService.getTasksForUser`)(function*(t){let n=yield*e.post(`/ajax/pln/GetTasksForUser`,{CONTACTID:t.contactId,START_DT:fe(t.startDate),PERIOD:t.period},U);return d.map(ue)(n.Records)}),getTodoDetail:t.fn(`BoardService.getTodoDetail`)(function*(t){return yield*e.post(`/ajax/board/GetTodoDetail`,{ID:t.id},G)}),getBoardTodos:t.fn(`BoardService.getBoardTodos`)(function*(t){let n=yield*e.post(`/ajax/board/GetTodos`,{BOARDID:t.boardId,BOARDLISTID:t.boardListId,limit:t.limit??100},K),i=d.map(J)(n.Records);return r.value(t.query).pipe(r.when(r.nonEmptyString,e=>{let t=f.toLowerCase(e);return d.filter(i,e=>e.name!==void 0&&f.includes(t)(f.toLowerCase(e.name)))}),r.orElse(()=>i))}),moveTodo:t.fn(`BoardService.moveTodo`)(function*(t){yield*e.post(`/ajax/board/MoveTodo`,{ID:t.id,BOARDID:t.boardId,BOARDLISTID:t.boardListId,SORTINDEX:t.sortIndex},o.Unknown)}),postMessage:t.fn(`BoardService.postMessage`)(function*(t){yield*e.post(`/ajax/board/ProcessMessage`,{ACTION:`ADD`,OBJECTTYPE:t.objectType,OBJECTID:t.objectId,CONTENT:t.content},o.Unknown)}),setTaskState:t.fn(`BoardService.setTaskState`)(function*(t){yield*e.post(`/ajax/pln/SetTaskState`,{PROJECTID:t.projectId,ID:t.taskId,STATE:t.state},o.Unknown)})})})),Z=t.gen(function*(){let e=yield*p.Redacted(`TLO_SESSION_TOKEN`),n=yield*p.String(`TLO_BASE_URL`).pipe(p.withDefault(`https://socialbrothers.orbit.teamleader.eu`)),r=yield*p.String(`TLO_COOKIES`).pipe(p.withDefault(``));return{baseUrl:n,sessionToken:e,cookies:yield*t.fromResult(c.setAll(c.empty,a.toEntries(c.parseHeader(r))))}}),pe=n.effect(h,Z),Q=n.mergeAll(A,X),$=C.pipe(n.provide(x.pipe(n.provide(m.layer)))),me=Q.pipe(n.provide($));export{g as C,y as D,v as E,h as O,C as S,ne as T,k as _,pe as a,E as b,I as c,W as d,G as f,J as g,N as h,Z as i,M as l,L as m,me as n,Y as o,q as p,Q as r,X as s,$ as t,B as u,A as v,_ as w,S as x,T as y};
|
package/dist/bin.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {}
|
package/dist/index.d.mts
DELETED
|
@@ -1,513 +0,0 @@
|
|
|
1
|
-
import * as Context from "effect/Context";
|
|
2
|
-
import * as Effect from "effect/Effect";
|
|
3
|
-
import * as Layer from "effect/Layer";
|
|
4
|
-
import * as Redacted from "effect/Redacted";
|
|
5
|
-
import * as Schema from "effect/Schema";
|
|
6
|
-
import * as Cookies from "effect/http/Cookies";
|
|
7
|
-
import * as HttpClient from "effect/http/HttpClient";
|
|
8
|
-
import * as Config from "effect/Config";
|
|
9
|
-
import * as Brand from "effect/Brand";
|
|
10
|
-
//#region src/services/TloConfig.d.ts
|
|
11
|
-
interface TloConfigShape {
|
|
12
|
-
readonly baseUrl: string;
|
|
13
|
-
readonly sessionToken: Redacted.Redacted;
|
|
14
|
-
readonly cookies: Cookies.Cookies;
|
|
15
|
-
}
|
|
16
|
-
declare const TloConfig_base: Context.ServiceClass<TloConfig, "@2digits/tlo-mcp/services/TloConfig", TloConfigShape>;
|
|
17
|
-
export declare class TloConfig extends TloConfig_base {}
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/schemas/errors.d.ts
|
|
20
|
-
declare const TloApiError_base: Schema.Class<TloApiError, Schema.TaggedStruct<"TloApiError", {
|
|
21
|
-
readonly message: Schema.String;
|
|
22
|
-
readonly code: Schema.optional<Schema.String>;
|
|
23
|
-
readonly endpoint: Schema.optional<Schema.String>;
|
|
24
|
-
}>, import("effect/Cause").YieldableError>;
|
|
25
|
-
/**
|
|
26
|
-
* Error returned when the TLO API indicates failure. This occurs when success: false in the response envelope.
|
|
27
|
-
*/
|
|
28
|
-
export declare class TloApiError extends TloApiError_base {}
|
|
29
|
-
declare const TloAuthError_base: Schema.Class<TloAuthError, Schema.TaggedStruct<"TloAuthError", {
|
|
30
|
-
readonly message: Schema.String;
|
|
31
|
-
}>, import("effect/Cause").YieldableError>;
|
|
32
|
-
/**
|
|
33
|
-
* Error for authentication failures. Session token expired or invalid credentials.
|
|
34
|
-
*/
|
|
35
|
-
export declare class TloAuthError extends TloAuthError_base {}
|
|
36
|
-
declare const TloNetworkError_base: Schema.Class<TloNetworkError, Schema.TaggedStruct<"TloNetworkError", {
|
|
37
|
-
readonly message: Schema.String;
|
|
38
|
-
readonly cause: Schema.optional<Schema.Unknown>;
|
|
39
|
-
readonly endpoint: Schema.optional<Schema.String>;
|
|
40
|
-
}>, import("effect/Cause").YieldableError>;
|
|
41
|
-
/**
|
|
42
|
-
* Error for network/transport failures. Connection refused, timeout, etc.
|
|
43
|
-
*/
|
|
44
|
-
export declare class TloNetworkError extends TloNetworkError_base {}
|
|
45
|
-
declare const TloParseError_base: Schema.Class<TloParseError, Schema.TaggedStruct<"TloParseError", {
|
|
46
|
-
readonly message: Schema.String;
|
|
47
|
-
readonly cause: Schema.optional<Schema.Unknown>;
|
|
48
|
-
}>, import("effect/Cause").YieldableError>;
|
|
49
|
-
/**
|
|
50
|
-
* Error when response parsing fails. Unexpected response format from TLO.
|
|
51
|
-
*/
|
|
52
|
-
export declare class TloParseError extends TloParseError_base {}
|
|
53
|
-
type TloError = TloApiError | TloAuthError | TloNetworkError | TloParseError;
|
|
54
|
-
//#endregion
|
|
55
|
-
//#region src/services/TloHttpClient.d.ts
|
|
56
|
-
interface TloHttpClientShape {
|
|
57
|
-
readonly client: HttpClient.HttpClient;
|
|
58
|
-
}
|
|
59
|
-
declare const TloHttpClient_base: Context.ServiceClass<TloHttpClient, "@2digits/tlo-mcp/services/TloHttpClient", TloHttpClientShape>;
|
|
60
|
-
declare class TloHttpClient extends TloHttpClient_base {}
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region src/services/TeamLeaderClient.d.ts
|
|
63
|
-
interface TeamLeaderClientShape {
|
|
64
|
-
readonly post: <TSchema extends Schema.Constraint>(path: string, body: Record<string, string | number | boolean | undefined>, schema: TSchema) => Effect.Effect<TSchema['Type'], TloError, TSchema['DecodingServices']>;
|
|
65
|
-
}
|
|
66
|
-
declare const TeamLeaderClient_base: Context.ServiceClass<TeamLeaderClient, "@2digits/tlo-mcp/services/TeamLeaderClient", TeamLeaderClientShape>;
|
|
67
|
-
export declare class TeamLeaderClient extends TeamLeaderClient_base {}
|
|
68
|
-
export declare const TeamLeaderClientLive: Layer.Layer<TeamLeaderClient, never, TloConfig | TloHttpClient>;
|
|
69
|
-
//#endregion
|
|
70
|
-
//#region src/schemas/time.d.ts
|
|
71
|
-
/**
|
|
72
|
-
* Raw activity from TLO API response. Field names match the API exactly.
|
|
73
|
-
*/
|
|
74
|
-
declare const ActivityRaw: Schema.Struct<{
|
|
75
|
-
readonly ID: Schema.Finite;
|
|
76
|
-
readonly FOLDERID: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
|
|
77
|
-
readonly PROJECT_ID: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
|
|
78
|
-
readonly TASKID: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Finite]>>>;
|
|
79
|
-
readonly TODOID: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Finite]>>>;
|
|
80
|
-
readonly TICKETID: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Finite]>>>;
|
|
81
|
-
readonly CONTACTID: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
|
|
82
|
-
readonly DESCRIPTION: Schema.NullOr<Schema.String>;
|
|
83
|
-
readonly DT: Schema.String;
|
|
84
|
-
readonly START_DT: Schema.optional<Schema.String>;
|
|
85
|
-
readonly END_DT: Schema.optional<Schema.String>;
|
|
86
|
-
readonly DURATION: Schema.Finite;
|
|
87
|
-
readonly TYPE: Schema.optional<Schema.Finite>;
|
|
88
|
-
readonly PROJECT_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
89
|
-
readonly TASK_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
90
|
-
readonly CLIENT_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
91
|
-
readonly FOLDER_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
92
|
-
readonly ENTITY_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
93
|
-
readonly PROJECTOWNER_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
94
|
-
readonly CLIENT_COLOR: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
95
|
-
readonly PROJECT_COLOR: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
96
|
-
readonly PROJECT_STATE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
97
|
-
readonly TASK_STATE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
98
|
-
readonly TIMESHEET_STATE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
99
|
-
readonly BILLING_MODE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
100
|
-
readonly NON_BILLABLE: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
101
|
-
readonly RATE: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
102
|
-
readonly VALUE: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
103
|
-
readonly PROJECT_START_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
104
|
-
readonly PROJECT_END_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
105
|
-
readonly TASK_START_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
106
|
-
readonly TASK_END_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
107
|
-
readonly READONLY: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
108
|
-
readonly TASK_WORKLOAD: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
109
|
-
}>;
|
|
110
|
-
type ActivityRaw = typeof ActivityRaw.Type;
|
|
111
|
-
declare const Activity_base: Schema.Class<Activity, Schema.Struct<{
|
|
112
|
-
readonly id: Schema.Finite;
|
|
113
|
-
readonly projectId: Schema.optional<Schema.String>;
|
|
114
|
-
readonly folderId: Schema.optional<Schema.String>;
|
|
115
|
-
readonly taskId: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
116
|
-
readonly todoId: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
117
|
-
readonly ticketId: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
118
|
-
readonly userId: Schema.optional<Schema.String>;
|
|
119
|
-
readonly description: Schema.NullOr<Schema.String>;
|
|
120
|
-
readonly startDate: Schema.String;
|
|
121
|
-
readonly endDate: Schema.optional<Schema.String>;
|
|
122
|
-
readonly durationMinutes: Schema.Finite;
|
|
123
|
-
readonly projectName: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
124
|
-
readonly taskName: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
125
|
-
readonly clientName: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
126
|
-
readonly folderName: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
127
|
-
readonly entityName: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
128
|
-
readonly projectOwnerName: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
129
|
-
readonly projectState: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
130
|
-
readonly taskState: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
131
|
-
readonly timesheetState: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
132
|
-
readonly billingMode: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
133
|
-
readonly nonBillable: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
134
|
-
readonly rate: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
135
|
-
readonly value: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
136
|
-
readonly projectStartDate: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
137
|
-
readonly projectEndDate: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
138
|
-
readonly taskStartDate: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
139
|
-
readonly taskEndDate: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
140
|
-
readonly readOnly: Schema.optional<Schema.NullOr<Schema.Boolean>>;
|
|
141
|
-
readonly taskWorkload: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
142
|
-
readonly projectColor: Schema.optional<Schema.String>;
|
|
143
|
-
readonly clientColor: Schema.optional<Schema.String>;
|
|
144
|
-
}>, {
|
|
145
|
-
readonly brand: unique symbol;
|
|
146
|
-
}>;
|
|
147
|
-
export declare class Activity extends Activity_base {}
|
|
148
|
-
export declare function activityFromRaw(raw: ActivityRaw): Activity;
|
|
149
|
-
declare const GetWeekResponse: Schema.Struct<{
|
|
150
|
-
readonly ACTIVITIES: Schema.$Array<Schema.Struct<{
|
|
151
|
-
readonly ID: Schema.Finite;
|
|
152
|
-
readonly FOLDERID: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
|
|
153
|
-
readonly PROJECT_ID: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
|
|
154
|
-
readonly TASKID: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Finite]>>>;
|
|
155
|
-
readonly TODOID: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Finite]>>>;
|
|
156
|
-
readonly TICKETID: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Finite]>>>;
|
|
157
|
-
readonly CONTACTID: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
|
|
158
|
-
readonly DESCRIPTION: Schema.NullOr<Schema.String>;
|
|
159
|
-
readonly DT: Schema.String;
|
|
160
|
-
readonly START_DT: Schema.optional<Schema.String>;
|
|
161
|
-
readonly END_DT: Schema.optional<Schema.String>;
|
|
162
|
-
readonly DURATION: Schema.Finite;
|
|
163
|
-
readonly TYPE: Schema.optional<Schema.Finite>;
|
|
164
|
-
readonly PROJECT_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
165
|
-
readonly TASK_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
166
|
-
readonly CLIENT_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
167
|
-
readonly FOLDER_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
168
|
-
readonly ENTITY_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
169
|
-
readonly PROJECTOWNER_NAME: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
170
|
-
readonly CLIENT_COLOR: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
171
|
-
readonly PROJECT_COLOR: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
172
|
-
readonly PROJECT_STATE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
173
|
-
readonly TASK_STATE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
174
|
-
readonly TIMESHEET_STATE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
175
|
-
readonly BILLING_MODE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
176
|
-
readonly NON_BILLABLE: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
177
|
-
readonly RATE: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
178
|
-
readonly VALUE: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
179
|
-
readonly PROJECT_START_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
180
|
-
readonly PROJECT_END_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
181
|
-
readonly TASK_START_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
182
|
-
readonly TASK_END_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
183
|
-
readonly READONLY: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
184
|
-
readonly TASK_WORKLOAD: Schema.optional<Schema.NullOr<Schema.Finite>>;
|
|
185
|
-
}>>;
|
|
186
|
-
}>;
|
|
187
|
-
type GetWeekResponse = typeof GetWeekResponse.Type;
|
|
188
|
-
declare const SetActivityResponse: Schema.Struct<{
|
|
189
|
-
readonly ID: Schema.Finite;
|
|
190
|
-
readonly DURATION: Schema.optional<Schema.Finite>;
|
|
191
|
-
readonly DT: Schema.optional<Schema.String>;
|
|
192
|
-
readonly TYPE: Schema.optional<Schema.Finite>;
|
|
193
|
-
readonly DESCRIPTION: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
194
|
-
readonly CLIENT_COLOR: Schema.optional<Schema.String>;
|
|
195
|
-
readonly FOLDERID: Schema.optional<Schema.Union<readonly [Schema.String, Schema.Finite]>>;
|
|
196
|
-
}>;
|
|
197
|
-
type SetActivityResponse = typeof SetActivityResponse.Type;
|
|
198
|
-
interface CreateActivityParams {
|
|
199
|
-
readonly folderId: string;
|
|
200
|
-
readonly taskId?: string;
|
|
201
|
-
readonly todoId?: string;
|
|
202
|
-
readonly startDate: Date;
|
|
203
|
-
readonly durationMinutes: number;
|
|
204
|
-
readonly description?: string;
|
|
205
|
-
readonly contactId: string;
|
|
206
|
-
readonly clientColor?: string;
|
|
207
|
-
}
|
|
208
|
-
interface UpdateActivityParams {
|
|
209
|
-
readonly id: number;
|
|
210
|
-
readonly startDate?: Date;
|
|
211
|
-
readonly durationMinutes?: number;
|
|
212
|
-
readonly description?: string;
|
|
213
|
-
}
|
|
214
|
-
interface DeleteActivityParams {
|
|
215
|
-
readonly id: number;
|
|
216
|
-
}
|
|
217
|
-
//#endregion
|
|
218
|
-
//#region src/services/TimeService.d.ts
|
|
219
|
-
interface TimeServiceShape {
|
|
220
|
-
readonly getWeek: (date: Date, contactId: string, timezone?: string) => Effect.Effect<ReadonlyArray<Activity>, TloError>;
|
|
221
|
-
readonly createActivity: (params: CreateActivityParams) => Effect.Effect<number, TloError>;
|
|
222
|
-
readonly updateActivity: (params: UpdateActivityParams) => Effect.Effect<void, TloError>;
|
|
223
|
-
readonly deleteActivity: (params: DeleteActivityParams) => Effect.Effect<void, TloError>;
|
|
224
|
-
}
|
|
225
|
-
declare const TimeService_base: Context.ServiceClass<TimeService, "@2digits/tlo-mcp/services/TimeService", TimeServiceShape>;
|
|
226
|
-
export declare class TimeService extends TimeService_base {}
|
|
227
|
-
export declare const TimeServiceLive: Layer.Layer<TimeService, never, TeamLeaderClient>;
|
|
228
|
-
//#endregion
|
|
229
|
-
//#region src/schemas/board.d.ts
|
|
230
|
-
type ProjectRaw = typeof ProjectRaw.Type;
|
|
231
|
-
declare const ProjectRaw: Schema.Struct<{
|
|
232
|
-
readonly ID: Schema.Finite;
|
|
233
|
-
readonly PROJECT_NAME: Schema.optional<Schema.String>;
|
|
234
|
-
readonly NAME: Schema.optional<Schema.String>;
|
|
235
|
-
readonly STATE: Schema.optional<Schema.String>;
|
|
236
|
-
readonly OWNER_NAME: Schema.optional<Schema.String>;
|
|
237
|
-
readonly START_DT: Schema.optional<Schema.String>;
|
|
238
|
-
readonly END_DT: Schema.optional<Schema.String>;
|
|
239
|
-
readonly BILLING_BUDGET: Schema.optional<Schema.Finite>;
|
|
240
|
-
readonly BILLING_MODE: Schema.optional<Schema.String>;
|
|
241
|
-
readonly BILLING_REMAINING: Schema.optional<Schema.Finite>;
|
|
242
|
-
readonly CLIENT_NAME: Schema.optional<Schema.String>;
|
|
243
|
-
readonly CLIENT_ID: Schema.optional<Schema.Finite>;
|
|
244
|
-
readonly FOLDERID: Schema.optional<Schema.Finite>;
|
|
245
|
-
readonly FOLDER_NAME: Schema.optional<Schema.String>;
|
|
246
|
-
readonly EXTID: Schema.optional<Schema.String>;
|
|
247
|
-
readonly CALC_TOTAL: Schema.optional<Schema.Finite>;
|
|
248
|
-
readonly CALC_DONE: Schema.optional<Schema.Finite>;
|
|
249
|
-
readonly CALC_PLANNED: Schema.optional<Schema.Finite>;
|
|
250
|
-
}>;
|
|
251
|
-
declare const Project_base: Schema.Class<Project, Schema.Struct<{
|
|
252
|
-
readonly id: Schema.Finite;
|
|
253
|
-
readonly name: Schema.String;
|
|
254
|
-
readonly state: Schema.optional<Schema.String>;
|
|
255
|
-
readonly ownerName: Schema.optional<Schema.String>;
|
|
256
|
-
readonly startDate: Schema.optional<Schema.String>;
|
|
257
|
-
readonly endDate: Schema.optional<Schema.String>;
|
|
258
|
-
readonly billingBudget: Schema.optional<Schema.Finite>;
|
|
259
|
-
readonly billingMode: Schema.optional<Schema.String>;
|
|
260
|
-
readonly billingRemaining: Schema.optional<Schema.Finite>;
|
|
261
|
-
readonly clientName: Schema.optional<Schema.String>;
|
|
262
|
-
readonly clientId: Schema.optional<Schema.Finite>;
|
|
263
|
-
readonly folderId: Schema.optional<Schema.Finite>;
|
|
264
|
-
readonly folderName: Schema.optional<Schema.String>;
|
|
265
|
-
readonly extId: Schema.optional<Schema.String>;
|
|
266
|
-
readonly calcTotal: Schema.optional<Schema.Finite>;
|
|
267
|
-
readonly calcDone: Schema.optional<Schema.Finite>;
|
|
268
|
-
readonly calcPlanned: Schema.optional<Schema.Finite>;
|
|
269
|
-
}>, {
|
|
270
|
-
readonly brand: unique symbol;
|
|
271
|
-
}>;
|
|
272
|
-
export declare class Project extends Project_base {}
|
|
273
|
-
export declare function projectFromRaw(raw: ProjectRaw): Project;
|
|
274
|
-
type MessageRaw = typeof MessageRaw.Type;
|
|
275
|
-
declare const MessageRaw: Schema.Struct<{
|
|
276
|
-
readonly ID: Schema.Finite;
|
|
277
|
-
readonly OWNERID: Schema.optional<Schema.Finite>;
|
|
278
|
-
readonly OWNER_NAME: Schema.optional<Schema.String>;
|
|
279
|
-
readonly AVATAR: Schema.optional<Schema.String>;
|
|
280
|
-
readonly CREATED_DT: Schema.optional<Schema.String>;
|
|
281
|
-
readonly CONTENT: Schema.optional<Schema.String>;
|
|
282
|
-
readonly SUBCONTENT: Schema.optional<Schema.String>;
|
|
283
|
-
readonly OBJECTTYPE: Schema.optional<Schema.String>;
|
|
284
|
-
readonly OBJECTID: Schema.optional<Schema.Finite>;
|
|
285
|
-
readonly OBJECTNAME: Schema.optional<Schema.String>;
|
|
286
|
-
readonly MSGTYPE: Schema.optional<Schema.String>;
|
|
287
|
-
readonly LIKECNT: Schema.optional<Schema.Finite>;
|
|
288
|
-
readonly REPLYCNT: Schema.optional<Schema.Finite>;
|
|
289
|
-
readonly OUTQUEUEID: Schema.optional<Schema.String>;
|
|
290
|
-
readonly INTERNAL: Schema.optional<Schema.Finite>;
|
|
291
|
-
}>;
|
|
292
|
-
declare const Message_base: Schema.Class<Message, Schema.Struct<{
|
|
293
|
-
readonly id: Schema.Finite;
|
|
294
|
-
readonly ownerId: Schema.optional<Schema.Finite>;
|
|
295
|
-
readonly ownerName: Schema.optional<Schema.String>;
|
|
296
|
-
readonly avatar: Schema.optional<Schema.String>;
|
|
297
|
-
readonly createdAt: Schema.optional<Schema.String>;
|
|
298
|
-
readonly content: Schema.optional<Schema.String>;
|
|
299
|
-
readonly subcontent: Schema.optional<Schema.String>;
|
|
300
|
-
readonly objectType: Schema.optional<Schema.String>;
|
|
301
|
-
readonly objectId: Schema.optional<Schema.Finite>;
|
|
302
|
-
readonly objectName: Schema.optional<Schema.String>;
|
|
303
|
-
readonly messageType: Schema.optional<Schema.String>;
|
|
304
|
-
readonly likeCount: Schema.optional<Schema.Finite>;
|
|
305
|
-
readonly replyCount: Schema.optional<Schema.Finite>;
|
|
306
|
-
readonly outQueueId: Schema.optional<Schema.String>;
|
|
307
|
-
readonly internal: Schema.optional<Schema.Boolean>;
|
|
308
|
-
}>, {
|
|
309
|
-
readonly brand: unique symbol;
|
|
310
|
-
}>;
|
|
311
|
-
export declare class Message extends Message_base {}
|
|
312
|
-
export declare function messageFromRaw(raw: MessageRaw): Message;
|
|
313
|
-
type TaskState = 'OPEN' | 'COMPLETED' | 'DRAFT' | 'CLOSED';
|
|
314
|
-
type TaskRaw = typeof TaskRaw.Type;
|
|
315
|
-
declare const TaskRaw: Schema.Struct<{
|
|
316
|
-
readonly ID: Schema.Finite;
|
|
317
|
-
readonly PROJECTID: Schema.optional<Schema.Finite>;
|
|
318
|
-
readonly NAME: Schema.optional<Schema.String>;
|
|
319
|
-
readonly STATE: Schema.optional<Schema.String>;
|
|
320
|
-
readonly OWNER_NAME: Schema.optional<Schema.String>;
|
|
321
|
-
readonly START_DT: Schema.optional<Schema.String>;
|
|
322
|
-
readonly END_DT: Schema.optional<Schema.String>;
|
|
323
|
-
readonly BUDGET: Schema.optional<Schema.Finite>;
|
|
324
|
-
}>;
|
|
325
|
-
declare const Task_base: Schema.Class<Task, Schema.Struct<{
|
|
326
|
-
readonly id: Schema.Finite;
|
|
327
|
-
readonly projectId: Schema.optional<Schema.Finite>;
|
|
328
|
-
readonly name: Schema.optional<Schema.String>;
|
|
329
|
-
readonly state: Schema.optional<Schema.String>;
|
|
330
|
-
readonly ownerName: Schema.optional<Schema.String>;
|
|
331
|
-
readonly startDate: Schema.optional<Schema.String>;
|
|
332
|
-
readonly endDate: Schema.optional<Schema.String>;
|
|
333
|
-
readonly budget: Schema.optional<Schema.Finite>;
|
|
334
|
-
}>, {
|
|
335
|
-
readonly brand: unique symbol;
|
|
336
|
-
}>;
|
|
337
|
-
export declare class Task extends Task_base {}
|
|
338
|
-
interface SetTaskStateParams {
|
|
339
|
-
readonly projectId: number;
|
|
340
|
-
readonly taskId: number;
|
|
341
|
-
readonly state: TaskState;
|
|
342
|
-
}
|
|
343
|
-
interface GetMessagesParams {
|
|
344
|
-
readonly objectType: 'TODO' | 'BOARD' | 'PROJECT';
|
|
345
|
-
readonly objectId: number;
|
|
346
|
-
readonly onlyComments?: boolean;
|
|
347
|
-
readonly hideInternal?: boolean;
|
|
348
|
-
readonly limit?: number;
|
|
349
|
-
}
|
|
350
|
-
interface GetProjectsParams {
|
|
351
|
-
readonly states?: ReadonlyArray<TaskState>;
|
|
352
|
-
readonly limit?: number;
|
|
353
|
-
readonly page?: number;
|
|
354
|
-
}
|
|
355
|
-
type TaskForUserRaw = typeof TaskForUserRaw.Type;
|
|
356
|
-
declare const TaskForUserRaw: Schema.Struct<{
|
|
357
|
-
readonly ID: Schema.Finite;
|
|
358
|
-
readonly WORKLOAD: Schema.optional<Schema.Finite>;
|
|
359
|
-
readonly TOTAL: Schema.optional<Schema.Finite>;
|
|
360
|
-
readonly REMAINING: Schema.optional<Schema.Finite>;
|
|
361
|
-
readonly AVAIL: Schema.optional<Schema.Finite>;
|
|
362
|
-
readonly CLIENTID: Schema.optional<Schema.Finite>;
|
|
363
|
-
readonly PROJECTID: Schema.optional<Schema.Finite>;
|
|
364
|
-
readonly PROJECT_START_DT: Schema.optional<Schema.String>;
|
|
365
|
-
readonly PROJECT_END_DT: Schema.optional<Schema.String>;
|
|
366
|
-
readonly START_DT: Schema.optional<Schema.String>;
|
|
367
|
-
readonly END_DT: Schema.optional<Schema.String>;
|
|
368
|
-
readonly STATE: Schema.optional<Schema.String>;
|
|
369
|
-
}>;
|
|
370
|
-
declare const TaskForUser_base: Schema.Class<TaskForUser, Schema.Struct<{
|
|
371
|
-
readonly id: Schema.Finite;
|
|
372
|
-
readonly workload: Schema.optional<Schema.Finite>;
|
|
373
|
-
readonly total: Schema.optional<Schema.Finite>;
|
|
374
|
-
readonly remaining: Schema.optional<Schema.Finite>;
|
|
375
|
-
readonly avail: Schema.optional<Schema.Finite>;
|
|
376
|
-
readonly clientId: Schema.optional<Schema.Finite>;
|
|
377
|
-
readonly projectId: Schema.optional<Schema.Finite>;
|
|
378
|
-
readonly projectStartDate: Schema.optional<Schema.String>;
|
|
379
|
-
readonly projectEndDate: Schema.optional<Schema.String>;
|
|
380
|
-
readonly startDate: Schema.optional<Schema.String>;
|
|
381
|
-
readonly endDate: Schema.optional<Schema.String>;
|
|
382
|
-
readonly state: Schema.optional<Schema.String>;
|
|
383
|
-
}>, {
|
|
384
|
-
readonly brand: unique symbol;
|
|
385
|
-
}>;
|
|
386
|
-
export declare class TaskForUser extends TaskForUser_base {}
|
|
387
|
-
export type TodoDetail = typeof TodoDetail.Type;
|
|
388
|
-
export declare const TodoDetail: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
389
|
-
type TodoSummaryRaw = typeof TodoSummaryRaw.Type;
|
|
390
|
-
declare const TodoSummaryRaw: Schema.Struct<{
|
|
391
|
-
readonly ID: Schema.Finite;
|
|
392
|
-
readonly NAME: Schema.optional<Schema.String>;
|
|
393
|
-
readonly BOARDLISTID: Schema.optional<Schema.Finite>;
|
|
394
|
-
readonly BOARDID: Schema.optional<Schema.Finite>;
|
|
395
|
-
readonly OWNERID: Schema.optional<Schema.Finite>;
|
|
396
|
-
readonly EXTID: Schema.optional<Schema.String>;
|
|
397
|
-
readonly STATE: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
398
|
-
readonly DUE_DT: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
399
|
-
readonly DESCRIPTION: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
400
|
-
}>;
|
|
401
|
-
declare const TodoSummary_base: Schema.Class<TodoSummary, Schema.Struct<{
|
|
402
|
-
readonly id: Schema.Finite;
|
|
403
|
-
readonly name: Schema.optional<Schema.String>;
|
|
404
|
-
readonly boardListId: Schema.optional<Schema.Finite>;
|
|
405
|
-
readonly boardId: Schema.optional<Schema.Finite>;
|
|
406
|
-
readonly ownerId: Schema.optional<Schema.Finite>;
|
|
407
|
-
readonly extId: Schema.optional<Schema.String>;
|
|
408
|
-
readonly state: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
409
|
-
readonly dueDate: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
410
|
-
readonly description: Schema.optional<Schema.NullOr<Schema.String>>;
|
|
411
|
-
}>, {
|
|
412
|
-
readonly brand: unique symbol;
|
|
413
|
-
}>;
|
|
414
|
-
export declare class TodoSummary extends TodoSummary_base {}
|
|
415
|
-
export declare function todoSummaryFromRaw(raw: TodoSummaryRaw): TodoSummary;
|
|
416
|
-
interface GetBoardTodosParams {
|
|
417
|
-
readonly boardId: number;
|
|
418
|
-
readonly boardListId?: number;
|
|
419
|
-
readonly query?: string;
|
|
420
|
-
readonly limit?: number;
|
|
421
|
-
}
|
|
422
|
-
interface GetTasksParams {
|
|
423
|
-
readonly projectId: number;
|
|
424
|
-
readonly states?: ReadonlyArray<TaskState>;
|
|
425
|
-
readonly limit?: number;
|
|
426
|
-
readonly page?: number;
|
|
427
|
-
}
|
|
428
|
-
interface GetProjectDetailsParams {
|
|
429
|
-
readonly projectId: number;
|
|
430
|
-
}
|
|
431
|
-
interface GetTasksForUserParams {
|
|
432
|
-
readonly contactId: string;
|
|
433
|
-
readonly startDate: Date;
|
|
434
|
-
readonly period: number;
|
|
435
|
-
}
|
|
436
|
-
interface GetTodoDetailParams {
|
|
437
|
-
readonly id: number;
|
|
438
|
-
}
|
|
439
|
-
interface MoveTodoParams {
|
|
440
|
-
readonly id: number;
|
|
441
|
-
readonly boardId: number;
|
|
442
|
-
readonly boardListId: number;
|
|
443
|
-
readonly sortIndex: number;
|
|
444
|
-
}
|
|
445
|
-
interface PostMessageParams {
|
|
446
|
-
readonly objectType: 'TODO' | 'BOARD' | 'PROJECT';
|
|
447
|
-
readonly objectId: number;
|
|
448
|
-
readonly content: string;
|
|
449
|
-
}
|
|
450
|
-
//#endregion
|
|
451
|
-
//#region src/services/BoardService.d.ts
|
|
452
|
-
interface BoardServiceShape {
|
|
453
|
-
readonly getProjects: (params?: GetProjectsParams) => Effect.Effect<ReadonlyArray<Project>, TloError>;
|
|
454
|
-
readonly getProjectDetails: (params: GetProjectDetailsParams) => Effect.Effect<Project, TloError>;
|
|
455
|
-
readonly getMessages: (params: GetMessagesParams) => Effect.Effect<ReadonlyArray<Message>, TloError>;
|
|
456
|
-
readonly getTasks: (params: GetTasksParams) => Effect.Effect<ReadonlyArray<Task>, TloError>;
|
|
457
|
-
readonly getTasksForUser: (params: GetTasksForUserParams) => Effect.Effect<ReadonlyArray<TaskForUser>, TloError>;
|
|
458
|
-
readonly getTodoDetail: (params: GetTodoDetailParams) => Effect.Effect<TodoDetail, TloError>;
|
|
459
|
-
readonly getBoardTodos: (params: GetBoardTodosParams) => Effect.Effect<ReadonlyArray<TodoSummary>, TloError>;
|
|
460
|
-
readonly moveTodo: (params: MoveTodoParams) => Effect.Effect<void, TloError>;
|
|
461
|
-
readonly postMessage: (params: PostMessageParams) => Effect.Effect<void, TloError>;
|
|
462
|
-
readonly setTaskState: (params: SetTaskStateParams) => Effect.Effect<void, TloError>;
|
|
463
|
-
}
|
|
464
|
-
declare const BoardService_base: Context.ServiceClass<BoardService, "@2digits/tlo-mcp/services/BoardService", BoardServiceShape>;
|
|
465
|
-
export declare class BoardService extends BoardService_base {}
|
|
466
|
-
export declare const BoardServiceLive: Layer.Layer<BoardService, never, TeamLeaderClient>;
|
|
467
|
-
//#endregion
|
|
468
|
-
//#region src/layers/TloConfigLive.d.ts
|
|
469
|
-
export declare const TloConfigFromEnv: Effect.Effect<TloConfigShape, Config.ConfigError | Cookies.CookiesError>;
|
|
470
|
-
export declare const TloConfigLive: Layer.Layer<TloConfig, Config.ConfigError | Cookies.CookiesError>;
|
|
471
|
-
//#endregion
|
|
472
|
-
//#region src/layers/TloLive.d.ts
|
|
473
|
-
export declare const TloServicesLive: Layer.Layer<BoardService | TimeService, never, TeamLeaderClient>;
|
|
474
|
-
export declare const TloClientLive: Layer.Layer<TeamLeaderClient, never, TloConfig>;
|
|
475
|
-
export declare const TloLive: Layer.Layer<TimeService | BoardService, never, TloConfig>;
|
|
476
|
-
//#endregion
|
|
477
|
-
//#region src/schemas/common.d.ts
|
|
478
|
-
/**
|
|
479
|
-
* TeamLeader Orbit uses YYYYMMDDHHMMSS format for dates. Example: "20251117143000" = Nov 17, 2025 14:30:00.
|
|
480
|
-
*/
|
|
481
|
-
export declare const TloDateString: Schema.brand<Schema.String, "TloDateString">;
|
|
482
|
-
export type TloDateString = typeof TloDateString.Type;
|
|
483
|
-
/**
|
|
484
|
-
* Format Date to YYYYMMDDHHMMSS string.
|
|
485
|
-
*
|
|
486
|
-
* @param date - Date to format.
|
|
487
|
-
*/
|
|
488
|
-
declare function formatTloDate(date: Date): TloDateString;
|
|
489
|
-
/**
|
|
490
|
-
* Transform between TLO date strings and JavaScript Date objects.
|
|
491
|
-
*/
|
|
492
|
-
export declare const TloDate: Schema.decodeTo<Schema.Date, Schema.brand<Schema.String, "TloDateString">, never, never>;
|
|
493
|
-
export type TloDate = typeof TloDate.Type;
|
|
494
|
-
/**
|
|
495
|
-
* TLO entity IDs are numeric strings.
|
|
496
|
-
*/
|
|
497
|
-
export type TloId = string & Brand.Brand<'TloId'>;
|
|
498
|
-
export declare const TloId: Brand.Constructor<TloId>;
|
|
499
|
-
export declare const TloIdSchema: Schema.brand<Schema.String, "TloId">;
|
|
500
|
-
/**
|
|
501
|
-
* Standard TLO API response envelope schema. Note: Most endpoints return data directly without this wrapper. Kept for
|
|
502
|
-
* potential use with endpoints that do use the envelope pattern.
|
|
503
|
-
*
|
|
504
|
-
* @param dataSchema - Data schema to wrap.
|
|
505
|
-
*/
|
|
506
|
-
export declare function TloResponse<T>(dataSchema: Schema.Schema<T>): Schema.Struct<{
|
|
507
|
-
readonly success: Schema.Boolean;
|
|
508
|
-
readonly ID: Schema.optional<Schema.Finite>;
|
|
509
|
-
readonly OBJ: Schema.Schema<T>;
|
|
510
|
-
readonly err: Schema.optional<Schema.String>;
|
|
511
|
-
}>;
|
|
512
|
-
//#endregion
|
|
513
|
-
export { type ActivityRaw, type BoardServiceShape, type CreateActivityParams, type DeleteActivityParams, type GetBoardTodosParams, type GetMessagesParams, type GetProjectDetailsParams, type GetProjectsParams, type GetTasksForUserParams, type GetTodoDetailParams, type GetWeekResponse, type MessageRaw, type MoveTodoParams, type PostMessageParams, type ProjectRaw, type SetActivityResponse, type SetTaskStateParams, type TaskForUserRaw, type TaskRaw, type TaskState, type TeamLeaderClientShape, type TimeServiceShape, type TloConfigShape, type TloError, type UpdateActivityParams, formatTloDate };
|
package/dist/index.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{C as e,D as t,E as n,O as r,S as i,_ as a,a as o,b as s,c,d as l,f as u,g as d,h as f,i as p,l as m,m as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S,v as C,w,x as T,y as E}from"./TloLive-DHG59kAi.mjs";import*as D from"effect/Schema";import*as O from"effect/Brand";import*as k from"effect/DateTime";import*as A from"effect/SchemaTransformation";const j=D.String.pipe(D.check(D.isPattern(/^\d{14}$/)),D.brand(`TloDateString`));function M(e){let t=Number.parseInt(e.slice(0,4),10),n=Number.parseInt(e.slice(4,6),10),r=Number.parseInt(e.slice(6,8),10),i=Number.parseInt(e.slice(8,10),10),a=Number.parseInt(e.slice(10,12),10),o=Number.parseInt(e.slice(12,14),10);return k.makeZonedUnsafe({year:t<=99?t+1900:t,month:n,day:r,hour:i,minute:a,second:o},{timeZone:k.zoneMakeLocal(),adjustForTimeZone:!0}).pipe(k.toDateUtc)}function N(e){let t=e.getFullYear().toString().padStart(4,`0`),n=(e.getMonth()+1).toString().padStart(2,`0`),r=e.getDate().toString().padStart(2,`0`),i=e.getHours().toString().padStart(2,`0`),a=e.getMinutes().toString().padStart(2,`0`),o=e.getSeconds().toString().padStart(2,`0`);return j.make(`${t}${n}${r}${i}${a}${o}`)}const P=j.pipe(D.decodeTo(D.Date,A.transform({decode:M,encode:N}))),F=O.nominal(),I=D.String.pipe(D.fromBrand(`TloId`,F));function L(e){return D.Struct({success:D.Boolean,ID:D.optional(D.Finite),OBJ:e,err:D.optional(D.String)})}export{E as Activity,_ as BoardService,b as BoardServiceLive,c as Message,m as Project,S as Task,l as TaskForUser,T as TeamLeaderClient,i as TeamLeaderClientLive,a as TimeService,C as TimeServiceLive,e as TloApiError,w as TloAuthError,x as TloClientLive,r as TloConfig,p as TloConfigFromEnv,o as TloConfigLive,P as TloDate,j as TloDateString,F as TloId,I as TloIdSchema,g as TloLive,n as TloNetworkError,t as TloParseError,L as TloResponse,y as TloServicesLive,u as TodoDetail,v as TodoSummary,s as activityFromRaw,N as formatTloDate,h as messageFromRaw,f as projectFromRaw,d as todoSummaryFromRaw};
|