A FirstMate Plugin that keeps what you work on from one Meeting to the next, and the free Notes that belong to no Meeting.
Read CONTEXT.md for the words this project uses — Entry, Note,
Status, Meeting, Cycle — and docs/adr/ for why a Cycle starts
when Scheduler says so and why the data is in SQLite.
One command, from its git URL. The files land in FirstMate's Shelf, the Plugin is registered in the same step, and nothing it ships is run:
firstmate install https://github.com/luanAfons0/worklog.git worklog
systemctl --user restart firstmateThen open the Index Page, find worklog, and open its Plugin Page.
To let Scheduler start a Cycle at every Meeting, record a Grant from Scheduler to Worklog and restart again. A Grant is one way and covers one pair:
firstmate grant scheduler worklog
systemctl --user restart firstmateDeveloping it instead? Register the directory where it already lives:
node src/cli.ts add worklog /absolute/path/to/worklog # in FirstMateSee CONTRIBUTING.md for the rest.
The Plugin Page has three tabs: Cycle, Earlier Cycles and Notes. The keys 1, 2 and 3 choose one, and N starts a new Entry. Drag an Entry to another column to give it that Status, or up and down to set its place in the column; the Meeting lists each section in that order. In a Note, Shift+Enter saves it.
Edit an Entry or a Note and its text stays formatted, as in Notion. Click a block to type in it: that block alone shows its Markdown, and it is formatted again the moment you leave it. Enter in a list starts the next item; Enter on an empty item or an empty line starts a new block. Tab and Shift+Tab indent a list item. Up and Down at the edge of a block go to the next one. Click a checkbox to tick it. Ctrl+click opens a link. The first Esc leaves the block, and the second closes the dialog.
It needs Node 24 or newer, for TypeScript with no build step and for
node:sqlite. The Host's service PATH often has only an older Node, so mcp
looks for one in this order: WORKLOG_NODE, node on PATH, then every Node
under nvm. With none, it says so in one sentence and the Plugin is Stopped.
- An Entry is one thing you work on: a title, an optional Markdown body,
a Status —
Todo,In Progress,In RevieworDone— and an optional Link: the http or https address of the issue or pull request it is about, in Linear, GitHub or elsewhere. The Entry card shows it as a small mark in its top-right corner (Linear's, GitHub's, or a plain link) that opens it in a new tab. - A Cycle is the time from one Meeting to the next, named by the moment it started. A new Entry goes into the current Cycle. The first Cycle starts by itself the first time Worklog needs one.
- When a Cycle starts, every Entry that is not
Donemoves into it with its Status unchanged. It is the same Entry, not a copy.DoneEntries stay in the Cycle they were done in. - An Entry that is not
Doneis always in the current Cycle. Set aDoneEntry in an earlier Cycle back to any other Status, and it moves into the current Cycle in the same write. - Every Cycle is kept. The Page lists the earlier ones, newest first, each named by the moment it started; open one to see what was Done in it.
- A Note is free Markdown text. It has no Status and belongs to no Cycle, so it never moves. Notes are listed newest first.
Everything lives in one file, worklog.db, in the Plugin directory. Git ignores
it. Worklog starts empty. A Plugin directory from before the rename to Worklog
holds daily.db instead; the first start renames it to worklog.db, with every
Cycle, Entry and Note in it.
web/new.html is a small second page: choose Entry or Note, type, press
Enter. An Entry has a title, a body, an optional Link and a Status that starts
at Todo; a Note has only a body. Shift+Enter is a new line in the body. After a save the page
goes to the main Plugin Page, ./.
It is the page a FirstMate Shortcut opens in a Popup. Going to ./ leaves
the Popup's address, so the Popup hides by itself. In a normal browser, at
/p/worklog/new.html, it lands on the main page instead. Once the Shortcut work
lands in FirstMate, bind it with:
node src/cli.ts bind <keys> worklog new.html # in FirstMateEvery tool answers its data twice: as JSON text, and as structuredContent.
A tool given bad input answers a JSON-RPC error with one sentence.
| Tool | Arguments | Answers |
|---|---|---|
start_cycle |
none | one sentence, and { cycle, moved, said } |
get_cycle |
optional id |
that Cycle, or the current one, and its Entries |
list_cycles |
none | { cycles }, newest first, each with counts by Status |
create_entry |
title, status, optional body, link |
the new Entry |
update_entry |
id, and any of title, body, status, link |
the Entry as it now is |
move_entry |
id, and status, before (optional) |
the Entry in its new place |
delete_entry |
id |
the Entry that is gone |
meeting_markdown |
none | the Markdown, and { markdown, done, inReview, workingOn } |
list_notes |
none | { notes }, newest first |
create_note |
body, title (optional) |
the new Note |
update_note |
id, body, title (optional) |
the Note as it now is |
delete_note |
id |
the Note that is gone |
start_cycle is the tool a Scheduler Job calls at every Meeting, under a
Grant from Scheduler to Worklog. Every call starts exactly one Cycle
(ADR-0001), and its text is one sentence, because a Scheduler Run keeps one:
Started the Cycle of Tue 23 Sep 10:00 and moved 4 Entries into it. The
moment is on the clock of the machine Worklog runs on. The Page's Start a new
Cycle button calls the same tool, after one question, for the day Scheduler
did not.
meeting_markdown decides what the Page's Start presentation shows: one
Entry at a time, full window, for the Statuses chosen at the top. It has a
"Done" section — the Entries Done in the Cycle before the current one, then
those already Done in the current one — an "In review" section, when
anything is In Review — and a "Working on" section — the current Cycle's
In Progress Entries, then its Todo ones, each in the order of its column. It lists titles only;
the title of an Entry with a Link is a Markdown link to it, [title](link). Present after the Meeting's start_cycle
has run; before, "Done" also holds what was reported last time.
Every Entry a tool answers carries link: its address, or null when it has
none. A link is an absolute http: or https: address of at most 2048
characters, kept trimmed; any other is refused. On update_entry, a link of
null or "" takes the Link away.
Any Status may go to any other. A body of null or "" takes the body
away. Bodies are Markdown; the Page shows them formatted with web/markdown.js,
a small renderer written for this Plugin, so the Page loads nothing from a CDN.
./mcp is the Plugin Server, as the Host runs it: MCP over stdio. npm test
runs every test; npm run typecheck checks the types.
MIT. See LICENSE.md.