Skip to main content

LLM Chat - Releases

← App details

Nextcloud 35

LLM Chat 2.6.0
Release Details
UpdatedSept. 7, 2026, 1:06 p.m.
Changelog

Added

  • Skills (issue #17), off by default. A skill is a Markdown file with YAML front matter, living in a Skills folder next to the archived chats — the user writes down how a particular kind of question should be answered, and the model follows it instead of improvising. Switching the setting on creates the folder together with an example skill for weather forecasts, which is the case that shows why this exists: asked for a forecast without it, a model runs a web search and summarises whatever a content farm published this morning; with it, the answer comes from one request to wttr.in's JSON API, laid out as a horizontal table because weather reads as a progression and one row per hour makes the reader scan vertically for a trend that runs sideways. Wind comes with a direction arrow — ↑ 12 — pointing where the air is heading, so a northerly is ↓. Same convention as wttr.in's own terminal output, and it reads like an arrow on a map. Note the half-turn it implies: winddir16Point names the direction the wind blows from, which is the opposite of the arrow, so the skill carries a lookup table rather than asking the model to flip a compass point in its head every time. Switching the setting off deletes nothing — the files are the user's, and a toggle is not consent to remove something they spent an evening writing.

Two halves, deliberately. Only the front matter — name and description, one line per skill — goes into the system prompt, so the model knows what exists; the body is fetched by skill_read when it decides a skill applies. Loading every body into every request would work for three skills and quietly cost a few thousand tokens per message at thirty. A skill without a description is not offered at all rather than listed by name, because the description is the thing the model chooses on.

A skill may declare allowed-domains, and skill_fetch will reach exactly those hosts, over https, straight from the browser — the same architecture as web search, and for the same reason: a weather question should not leave a line in the server's access log naming the town. Those hosts go into the page's CSP, which is what makes the request possible at all and means a newly added skill needs a reload before its host can be reached, as with connections. Subdomains do not inherit and wildcards are rejected: wttr.in does not grant evil.wttr.in. Neither tool asks for approval — writing allowed-domains in a file you own is the approval, given once instead of on every weather question, and that is a far narrower thing than web_fetch, which takes any URL a model can invent and therefore always asks.

It also sidesteps a limit that would have made the example skill quietly wrong: wttr.in's three-day JSON is about 27 000 characters once whitespace is normalised, and web_fetch caps text at 24 000. Routed through the server, the third day would simply have been missing, with nothing in the answer to say so. - "LLM Chat" in the Files app menu (issue #20). The entry opens a new chat in a new tab with the file's path already quoted in the prompt, ready for a question to be typed after it — the same treatment the composer's own file picker gives a path, since it has to survive being read back by a model and turned into a tool argument. It does not send anything: a path without a question is not a request, and guessing "summarise this" would be wrong about half the time. Shown for folders as well as files, because the path only goes into the prompt and a folder is a perfectly good argument for nc_list_files. It ships as its own 1 kB bundle rather than being folded into the app's: this code loads on every page of the Files app, and the main bundle is a chat client that brings Vue, pinia and pdf.js with it.

Changed

  • quotePath moved out of the composer into services/paths.js, since the Files action needs exactly the same quoting and a second copy would drift from the first.
  • The reload notice no longer claims a connection changed. It fires for a connection, for the SearXNG URL and now for a skill's allowed-domains alike, so saying "a connection was added" after switching skills on was simply untrue. The remedy is identical in every case, so the message names the remedy and not a cause it cannot know.

Fixed

  • package-lock.json said 2.4.0 — it was missed in the 2.5.0 release and is now regenerated along with the version bump. It also gains @nextcloud/files, which the Files action imports directly; it was previously only present transitively via @nextcloud/dialogs, so npm ci happened to work while the lock file did not actually guarantee it.
  • Listing the skills checks each file's size before reading it. The size check existed only on the read path, so a large file that happened to sit in the skills folder was pulled into memory in full on every page load just to have its front matter looked at — the listing runs while the page is being rendered. Oversized files are now skipped on both paths rather than refused on one of them, so the catalogue and a later read cannot disagree about which file answers for an id.
  • Two files whose names differ only in case no longer produce two entries for one skill. Ids are compared case-insensitively, so Weather.md and weather.md are the same skill to the model while skill_read answers with whichever the directory listing yields first; offering both was offering a coin flip.
  • skill_fetch rejects a URL with an explicit port instead of letting it fail as a network error. The CSP entry is https://host, which covers the default port and nothing else, so https://example.com:8443/… passed the host check and was then blocked by the browser — and reported as the generic "reload the page, or the host does not send CORS headers", which is neither the cause nor a hint at the fix.
  • Switching skills on now switches back off when the folder cannot be created. The setting and the provisioning are two calls, and if the second failed the switch stayed on next to an empty list with nothing saying why.
  • The front matter terminator has to be a line of its own. Scanning for \n--- also stopped at ---- or --- something and left the remainder of that line at the top of the body. Extra dashes and trailing whitespace are tolerated, since an editor produces those by accident.
  • A ?path= longer than the 4000-character cap is truncated before being quoted rather than after, which used to cut off the closing quote and leave the path running into whatever was typed next.

Removed

  • SkillService::domains(), which had no callers: PageController derives the CSP hosts from the same listing it puts into the initial state, deliberately, so the method was a second implementation waiting to disagree with the first.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEAjCCAuoCAhP6MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxNTExWhcNMzYxMjEwMTMxNTExWjASMRAwDgYD
VQQDDAdsbG1jaGF0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7HDs
jKp7FsGKcuel3l3CTv8nfsIZXOMCyn2/kR0cZ18lnjrsxstIV/kB+t/lHD/V63Sp
weVFyxdOR5suH1ZYyc2iAGNtghJOq/hijKwSCNeJfSijyaHPOThIQaNDF30hDrSe
zThc3KidVfRfLz+FDJl18KzUJvYYRBDrBpd70UuDuiVoDu2mNPsUMx/ubQzLCCA+
lGOcWMAV8wcBi3SE/CNciqDvzU1WAIxl0KcUzCdF924IyG7XcRktuSdDXxMEPiV4
r3FYkUVZLTMkjT6gOzWNGt0i5pjJNFV0WYo3+OnaT3Ra2tCxSFYPGUhptxsY6xTu
gLOkSc5SOT8CSsFS7fjERutvt8C0oHEbu5N7TDoywBIcgu+oSe6pGuI12D8WAt+C
EuLs1OM4i7yUETtYoeURPWXDyhhu58Lfl89EFOfKjwhuySDhX6ALV8d559hPD9VY
OU/zv6j6De0m89lYgWXyoF0ClT0iu5OXqFO5NQuW8ZnXN2rZVvVNapMuYXSWdeL/
qi/xzivNsS++fZrRAO8kfGViN8wrcPcw4laKw/82iTsI9rldsimXkMQx2rXDw+4/
dCor1rdv3pAKv0xODI6bvLO3/df5DjlDHqAl9H++E2SiXciII+4abLQ+1lP8Ebsi
4e9lVt6ypI8Q3AeQunGWIiFmCtmKmM22jvgMTEsCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAffSGNpUST/kNZvVzcELBSu7RY77qo2MmkJ+zgo+4raXq66G3EITIIHyA
vbx2Mrok18l+xLNhd+m37+1W31/HRdKIsldma2qfb1eVw9bDH927LdHjFPKcZuIu
d6o3y6DS+jcYC01ghT9eDo4ammi5xUDJrriDkA1FAQl8VDhofCe76U40GSZTlg8f
uON0WiFfRuA7IG8Trw2DcQFEHEi7ufVEV/OUFdQoDAtIljunOm4iXV9pRLdj1Um6
UYKKERhs3/oIUjuFSbuBQgUBo7LCt5q69K64hfwexNax/pEkIR8kjzdtnUdqcAhX
3nq8ZcjtzKgN3LRYfYUEgRYAyEqthQ==
-----END CERTIFICATE-----
SignaturepnJ++Eu59i529AKxkzd2tO2dpTjv3Fo7Xbb7+Dvro4ABDBzRJooeHH8t7/90T3UVLojwiKbPAdBlzftVowWP0JLDIjoa/5u9NyFiH0OfIgRBPeg9n4EFbnvm84m1onW79hs5VLK7NFj7SVtix6DAYb120mu4E8Nw5iWCHC7ok13dOcEihOuhNqVTb5ubnkiBiaTYHMn8ai8GLcfrqyitsj1AHv51rWe6CxDAJXqJdzeVGA9Z8/q3YnPrZChColNe5HbDkmM7Op7tOJy8AOvnBVlvaRFc2f3aBSKBi/u6BkgY3o6F899zMj1MYkfu+CHf3AGYo06RL0LLax3o8AU6lQ0hs/Vq3meeyXKJ/dh6G3UGZIApTAxc5gQ5C2e+Y+WkhAJtLBF8OTvTWKXUCGhmVrDnSbaUk1bFAOW3US/7OdZgHWfZ6QomhR2JNfteCtkpD5hISJspmf2z5z0H/9wLDR1wxAsNSGoCbHphFd56csfDpd16h7z6td1Map66XJeweNtcZJfMj1YjhhrbR0uMUyDRpMdfBKHxTcAouMZf91q5hAmenVQjSEqvtC9vlX5Gm7AEUdYzRP20DsndWgRl2ZOEIwfNF8F4RzGHGfYRUWP/bKTHAFORR1PPc77Qvg5GfL5VA5QhvhIX2d8aNPwHbtDNUAhP3MkaiEfVS1QUcr0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=34.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.3.0
LLM Chat 2.5.0
Release Details
UpdatedSept. 7, 2026, 6:33 a.m.
Changelog

Added

  • The settings modal has a footer with the installed version and a link to the repository (issue #19). The version is read from the app manager rather than compiled into the bundle: a constant next to info.xml is a second place to forget on release day, and it would report what the assets were built from instead of what is actually installed — which is exactly the wrong number on a report from someone who deployed by rsync. It sits below the tab strip rather than inside the general tab, because it identifies the app and not the chat settings, and a report written while looking at the connections tab needs it just as much.

Changed

  • Nextcloud 35 is supported (issue #21): max-version moves to 35 while 34 stays in. Nothing in the app had to change for it — every OCP interface it touches still exists, IUserConfig is the current API rather than the IConfig methods deprecated in 33, and no removal in 35 affects it.
  • PHP 8.3 is now the minimum, up from 8.2. Nextcloud 35 requires it regardless, so keeping 8.2 in the manifest would promise something half the supported range cannot deliver. Running 34 on 8.3 is an ordinary combination, so this drops no installation that could otherwise have had 2.5.0 — the app store resolves <php min-version> against the server, not against Nextcloud's own floor.
  • The @NoAdminRequired and @NoCSRFRequired docblock annotations are gone; the matching #[…] attributes were already there and are what actually grants access since Nextcloud 27. Keeping both meant every request logged a deprecation notice on the annotation path — harmless, and noise in the debug log for no benefit, since the attributes short-circuit the check before the annotation is ever read.
  • The <screenshot> URLs in info.xml point at latest instead of main, following the rename of this repository's default branch. The old URLs 404, which the store does not report — it just renders empty tiles in the gallery.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEAjCCAuoCAhP6MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxNTExWhcNMzYxMjEwMTMxNTExWjASMRAwDgYD
VQQDDAdsbG1jaGF0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7HDs
jKp7FsGKcuel3l3CTv8nfsIZXOMCyn2/kR0cZ18lnjrsxstIV/kB+t/lHD/V63Sp
weVFyxdOR5suH1ZYyc2iAGNtghJOq/hijKwSCNeJfSijyaHPOThIQaNDF30hDrSe
zThc3KidVfRfLz+FDJl18KzUJvYYRBDrBpd70UuDuiVoDu2mNPsUMx/ubQzLCCA+
lGOcWMAV8wcBi3SE/CNciqDvzU1WAIxl0KcUzCdF924IyG7XcRktuSdDXxMEPiV4
r3FYkUVZLTMkjT6gOzWNGt0i5pjJNFV0WYo3+OnaT3Ra2tCxSFYPGUhptxsY6xTu
gLOkSc5SOT8CSsFS7fjERutvt8C0oHEbu5N7TDoywBIcgu+oSe6pGuI12D8WAt+C
EuLs1OM4i7yUETtYoeURPWXDyhhu58Lfl89EFOfKjwhuySDhX6ALV8d559hPD9VY
OU/zv6j6De0m89lYgWXyoF0ClT0iu5OXqFO5NQuW8ZnXN2rZVvVNapMuYXSWdeL/
qi/xzivNsS++fZrRAO8kfGViN8wrcPcw4laKw/82iTsI9rldsimXkMQx2rXDw+4/
dCor1rdv3pAKv0xODI6bvLO3/df5DjlDHqAl9H++E2SiXciII+4abLQ+1lP8Ebsi
4e9lVt6ypI8Q3AeQunGWIiFmCtmKmM22jvgMTEsCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAffSGNpUST/kNZvVzcELBSu7RY77qo2MmkJ+zgo+4raXq66G3EITIIHyA
vbx2Mrok18l+xLNhd+m37+1W31/HRdKIsldma2qfb1eVw9bDH927LdHjFPKcZuIu
d6o3y6DS+jcYC01ghT9eDo4ammi5xUDJrriDkA1FAQl8VDhofCe76U40GSZTlg8f
uON0WiFfRuA7IG8Trw2DcQFEHEi7ufVEV/OUFdQoDAtIljunOm4iXV9pRLdj1Um6
UYKKERhs3/oIUjuFSbuBQgUBo7LCt5q69K64hfwexNax/pEkIR8kjzdtnUdqcAhX
3nq8ZcjtzKgN3LRYfYUEgRYAyEqthQ==
-----END CERTIFICATE-----
Signature5Ub1MIaWfiN8Lq8zIfJC3K0wORQFHjDjVWloXNgWyh9hEAyhNEyGxQJTzB/anptAnkS215vnCohtdrG7gU04PdDsbKHmUXZIgJAoId2wOsy3dMtc/M1DURafzoimtd41VQHr5V5qtl+oiGwBP/eycP9d88TjtUxU2rrwSKZ0D2PaTEqIX2xUKl1MeUtFA8g3DhVPA+xuZ2Cg6UH6Ho6Ak+Lbl+MoYFfkD+5ajYvpkoyzthZ5ZfUGVGoBgni3s1s0tUKcpagOInwMOLj3/GFbUcXbegcjZMeBimfsX41PRzMMvQ4n9a9je4i4gXiTQ1g0uDhxy0wsyVdF8Oqhqa0VdP2NV0xSZtdEhPw6ryxISuGqLlyZ9jYC5AMTkU3kFJcvcEw/t7l6U1NXjq5laadpqTI5r3pXkfbyAuRb+8COfkvCQy8tb/Cn7C1J+2ASFOt6+NDb8yVEAvkjgFTGno2bRAdkvqNtCNQhC+K4BWpMRtjyZTETrCxrXE1azt5dTbU5nKQb6Gp9zQ7Q4xhvY9uGbkrzJLejvlmonDF+yoUVq2nHB8Mfzaza3Zn2P5FIxKV6JY/yQgGooLP8Yr/IXJZkCiWeWE/QU2ocjvWfCVX5Po5n5/LRXFqi1KU2tTcd5iuV8FydcYblgrNNgkyAHnAsQfZ8QJ0MmO7Uko43ONFLoZs=
Signature digestsha512
Dependencies
Required Nextcloud versions >=34.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.3.0

Nextcloud 34

LLM Chat 2.6.0
Release Details
UpdatedSept. 7, 2026, 1:06 p.m.
Changelog

Added

  • Skills (issue #17), off by default. A skill is a Markdown file with YAML front matter, living in a Skills folder next to the archived chats — the user writes down how a particular kind of question should be answered, and the model follows it instead of improvising. Switching the setting on creates the folder together with an example skill for weather forecasts, which is the case that shows why this exists: asked for a forecast without it, a model runs a web search and summarises whatever a content farm published this morning; with it, the answer comes from one request to wttr.in's JSON API, laid out as a horizontal table because weather reads as a progression and one row per hour makes the reader scan vertically for a trend that runs sideways. Wind comes with a direction arrow — ↑ 12 — pointing where the air is heading, so a northerly is ↓. Same convention as wttr.in's own terminal output, and it reads like an arrow on a map. Note the half-turn it implies: winddir16Point names the direction the wind blows from, which is the opposite of the arrow, so the skill carries a lookup table rather than asking the model to flip a compass point in its head every time. Switching the setting off deletes nothing — the files are the user's, and a toggle is not consent to remove something they spent an evening writing.

Two halves, deliberately. Only the front matter — name and description, one line per skill — goes into the system prompt, so the model knows what exists; the body is fetched by skill_read when it decides a skill applies. Loading every body into every request would work for three skills and quietly cost a few thousand tokens per message at thirty. A skill without a description is not offered at all rather than listed by name, because the description is the thing the model chooses on.

A skill may declare allowed-domains, and skill_fetch will reach exactly those hosts, over https, straight from the browser — the same architecture as web search, and for the same reason: a weather question should not leave a line in the server's access log naming the town. Those hosts go into the page's CSP, which is what makes the request possible at all and means a newly added skill needs a reload before its host can be reached, as with connections. Subdomains do not inherit and wildcards are rejected: wttr.in does not grant evil.wttr.in. Neither tool asks for approval — writing allowed-domains in a file you own is the approval, given once instead of on every weather question, and that is a far narrower thing than web_fetch, which takes any URL a model can invent and therefore always asks.

It also sidesteps a limit that would have made the example skill quietly wrong: wttr.in's three-day JSON is about 27 000 characters once whitespace is normalised, and web_fetch caps text at 24 000. Routed through the server, the third day would simply have been missing, with nothing in the answer to say so. - "LLM Chat" in the Files app menu (issue #20). The entry opens a new chat in a new tab with the file's path already quoted in the prompt, ready for a question to be typed after it — the same treatment the composer's own file picker gives a path, since it has to survive being read back by a model and turned into a tool argument. It does not send anything: a path without a question is not a request, and guessing "summarise this" would be wrong about half the time. Shown for folders as well as files, because the path only goes into the prompt and a folder is a perfectly good argument for nc_list_files. It ships as its own 1 kB bundle rather than being folded into the app's: this code loads on every page of the Files app, and the main bundle is a chat client that brings Vue, pinia and pdf.js with it.

Changed

  • quotePath moved out of the composer into services/paths.js, since the Files action needs exactly the same quoting and a second copy would drift from the first.
  • The reload notice no longer claims a connection changed. It fires for a connection, for the SearXNG URL and now for a skill's allowed-domains alike, so saying "a connection was added" after switching skills on was simply untrue. The remedy is identical in every case, so the message names the remedy and not a cause it cannot know.

Fixed

  • package-lock.json said 2.4.0 — it was missed in the 2.5.0 release and is now regenerated along with the version bump. It also gains @nextcloud/files, which the Files action imports directly; it was previously only present transitively via @nextcloud/dialogs, so npm ci happened to work while the lock file did not actually guarantee it.
  • Listing the skills checks each file's size before reading it. The size check existed only on the read path, so a large file that happened to sit in the skills folder was pulled into memory in full on every page load just to have its front matter looked at — the listing runs while the page is being rendered. Oversized files are now skipped on both paths rather than refused on one of them, so the catalogue and a later read cannot disagree about which file answers for an id.
  • Two files whose names differ only in case no longer produce two entries for one skill. Ids are compared case-insensitively, so Weather.md and weather.md are the same skill to the model while skill_read answers with whichever the directory listing yields first; offering both was offering a coin flip.
  • skill_fetch rejects a URL with an explicit port instead of letting it fail as a network error. The CSP entry is https://host, which covers the default port and nothing else, so https://example.com:8443/… passed the host check and was then blocked by the browser — and reported as the generic "reload the page, or the host does not send CORS headers", which is neither the cause nor a hint at the fix.
  • Switching skills on now switches back off when the folder cannot be created. The setting and the provisioning are two calls, and if the second failed the switch stayed on next to an empty list with nothing saying why.
  • The front matter terminator has to be a line of its own. Scanning for \n--- also stopped at ---- or --- something and left the remainder of that line at the top of the body. Extra dashes and trailing whitespace are tolerated, since an editor produces those by accident.
  • A ?path= longer than the 4000-character cap is truncated before being quoted rather than after, which used to cut off the closing quote and leave the path running into whatever was typed next.

Removed

  • SkillService::domains(), which had no callers: PageController derives the CSP hosts from the same listing it puts into the initial state, deliberately, so the method was a second implementation waiting to disagree with the first.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEAjCCAuoCAhP6MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxNTExWhcNMzYxMjEwMTMxNTExWjASMRAwDgYD
VQQDDAdsbG1jaGF0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7HDs
jKp7FsGKcuel3l3CTv8nfsIZXOMCyn2/kR0cZ18lnjrsxstIV/kB+t/lHD/V63Sp
weVFyxdOR5suH1ZYyc2iAGNtghJOq/hijKwSCNeJfSijyaHPOThIQaNDF30hDrSe
zThc3KidVfRfLz+FDJl18KzUJvYYRBDrBpd70UuDuiVoDu2mNPsUMx/ubQzLCCA+
lGOcWMAV8wcBi3SE/CNciqDvzU1WAIxl0KcUzCdF924IyG7XcRktuSdDXxMEPiV4
r3FYkUVZLTMkjT6gOzWNGt0i5pjJNFV0WYo3+OnaT3Ra2tCxSFYPGUhptxsY6xTu
gLOkSc5SOT8CSsFS7fjERutvt8C0oHEbu5N7TDoywBIcgu+oSe6pGuI12D8WAt+C
EuLs1OM4i7yUETtYoeURPWXDyhhu58Lfl89EFOfKjwhuySDhX6ALV8d559hPD9VY
OU/zv6j6De0m89lYgWXyoF0ClT0iu5OXqFO5NQuW8ZnXN2rZVvVNapMuYXSWdeL/
qi/xzivNsS++fZrRAO8kfGViN8wrcPcw4laKw/82iTsI9rldsimXkMQx2rXDw+4/
dCor1rdv3pAKv0xODI6bvLO3/df5DjlDHqAl9H++E2SiXciII+4abLQ+1lP8Ebsi
4e9lVt6ypI8Q3AeQunGWIiFmCtmKmM22jvgMTEsCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAffSGNpUST/kNZvVzcELBSu7RY77qo2MmkJ+zgo+4raXq66G3EITIIHyA
vbx2Mrok18l+xLNhd+m37+1W31/HRdKIsldma2qfb1eVw9bDH927LdHjFPKcZuIu
d6o3y6DS+jcYC01ghT9eDo4ammi5xUDJrriDkA1FAQl8VDhofCe76U40GSZTlg8f
uON0WiFfRuA7IG8Trw2DcQFEHEi7ufVEV/OUFdQoDAtIljunOm4iXV9pRLdj1Um6
UYKKERhs3/oIUjuFSbuBQgUBo7LCt5q69K64hfwexNax/pEkIR8kjzdtnUdqcAhX
3nq8ZcjtzKgN3LRYfYUEgRYAyEqthQ==
-----END CERTIFICATE-----
SignaturepnJ++Eu59i529AKxkzd2tO2dpTjv3Fo7Xbb7+Dvro4ABDBzRJooeHH8t7/90T3UVLojwiKbPAdBlzftVowWP0JLDIjoa/5u9NyFiH0OfIgRBPeg9n4EFbnvm84m1onW79hs5VLK7NFj7SVtix6DAYb120mu4E8Nw5iWCHC7ok13dOcEihOuhNqVTb5ubnkiBiaTYHMn8ai8GLcfrqyitsj1AHv51rWe6CxDAJXqJdzeVGA9Z8/q3YnPrZChColNe5HbDkmM7Op7tOJy8AOvnBVlvaRFc2f3aBSKBi/u6BkgY3o6F899zMj1MYkfu+CHf3AGYo06RL0LLax3o8AU6lQ0hs/Vq3meeyXKJ/dh6G3UGZIApTAxc5gQ5C2e+Y+WkhAJtLBF8OTvTWKXUCGhmVrDnSbaUk1bFAOW3US/7OdZgHWfZ6QomhR2JNfteCtkpD5hISJspmf2z5z0H/9wLDR1wxAsNSGoCbHphFd56csfDpd16h7z6td1Map66XJeweNtcZJfMj1YjhhrbR0uMUyDRpMdfBKHxTcAouMZf91q5hAmenVQjSEqvtC9vlX5Gm7AEUdYzRP20DsndWgRl2ZOEIwfNF8F4RzGHGfYRUWP/bKTHAFORR1PPc77Qvg5GfL5VA5QhvhIX2d8aNPwHbtDNUAhP3MkaiEfVS1QUcr0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=34.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.3.0
LLM Chat 2.5.0
Release Details
UpdatedSept. 7, 2026, 6:33 a.m.
Changelog

Added

  • The settings modal has a footer with the installed version and a link to the repository (issue #19). The version is read from the app manager rather than compiled into the bundle: a constant next to info.xml is a second place to forget on release day, and it would report what the assets were built from instead of what is actually installed — which is exactly the wrong number on a report from someone who deployed by rsync. It sits below the tab strip rather than inside the general tab, because it identifies the app and not the chat settings, and a report written while looking at the connections tab needs it just as much.

Changed

  • Nextcloud 35 is supported (issue #21): max-version moves to 35 while 34 stays in. Nothing in the app had to change for it — every OCP interface it touches still exists, IUserConfig is the current API rather than the IConfig methods deprecated in 33, and no removal in 35 affects it.
  • PHP 8.3 is now the minimum, up from 8.2. Nextcloud 35 requires it regardless, so keeping 8.2 in the manifest would promise something half the supported range cannot deliver. Running 34 on 8.3 is an ordinary combination, so this drops no installation that could otherwise have had 2.5.0 — the app store resolves <php min-version> against the server, not against Nextcloud's own floor.
  • The @NoAdminRequired and @NoCSRFRequired docblock annotations are gone; the matching #[…] attributes were already there and are what actually grants access since Nextcloud 27. Keeping both meant every request logged a deprecation notice on the annotation path — harmless, and noise in the debug log for no benefit, since the attributes short-circuit the check before the annotation is ever read.
  • The <screenshot> URLs in info.xml point at latest instead of main, following the rename of this repository's default branch. The old URLs 404, which the store does not report — it just renders empty tiles in the gallery.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEAjCCAuoCAhP6MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxNTExWhcNMzYxMjEwMTMxNTExWjASMRAwDgYD
VQQDDAdsbG1jaGF0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7HDs
jKp7FsGKcuel3l3CTv8nfsIZXOMCyn2/kR0cZ18lnjrsxstIV/kB+t/lHD/V63Sp
weVFyxdOR5suH1ZYyc2iAGNtghJOq/hijKwSCNeJfSijyaHPOThIQaNDF30hDrSe
zThc3KidVfRfLz+FDJl18KzUJvYYRBDrBpd70UuDuiVoDu2mNPsUMx/ubQzLCCA+
lGOcWMAV8wcBi3SE/CNciqDvzU1WAIxl0KcUzCdF924IyG7XcRktuSdDXxMEPiV4
r3FYkUVZLTMkjT6gOzWNGt0i5pjJNFV0WYo3+OnaT3Ra2tCxSFYPGUhptxsY6xTu
gLOkSc5SOT8CSsFS7fjERutvt8C0oHEbu5N7TDoywBIcgu+oSe6pGuI12D8WAt+C
EuLs1OM4i7yUETtYoeURPWXDyhhu58Lfl89EFOfKjwhuySDhX6ALV8d559hPD9VY
OU/zv6j6De0m89lYgWXyoF0ClT0iu5OXqFO5NQuW8ZnXN2rZVvVNapMuYXSWdeL/
qi/xzivNsS++fZrRAO8kfGViN8wrcPcw4laKw/82iTsI9rldsimXkMQx2rXDw+4/
dCor1rdv3pAKv0xODI6bvLO3/df5DjlDHqAl9H++E2SiXciII+4abLQ+1lP8Ebsi
4e9lVt6ypI8Q3AeQunGWIiFmCtmKmM22jvgMTEsCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAffSGNpUST/kNZvVzcELBSu7RY77qo2MmkJ+zgo+4raXq66G3EITIIHyA
vbx2Mrok18l+xLNhd+m37+1W31/HRdKIsldma2qfb1eVw9bDH927LdHjFPKcZuIu
d6o3y6DS+jcYC01ghT9eDo4ammi5xUDJrriDkA1FAQl8VDhofCe76U40GSZTlg8f
uON0WiFfRuA7IG8Trw2DcQFEHEi7ufVEV/OUFdQoDAtIljunOm4iXV9pRLdj1Um6
UYKKERhs3/oIUjuFSbuBQgUBo7LCt5q69K64hfwexNax/pEkIR8kjzdtnUdqcAhX
3nq8ZcjtzKgN3LRYfYUEgRYAyEqthQ==
-----END CERTIFICATE-----
Signature5Ub1MIaWfiN8Lq8zIfJC3K0wORQFHjDjVWloXNgWyh9hEAyhNEyGxQJTzB/anptAnkS215vnCohtdrG7gU04PdDsbKHmUXZIgJAoId2wOsy3dMtc/M1DURafzoimtd41VQHr5V5qtl+oiGwBP/eycP9d88TjtUxU2rrwSKZ0D2PaTEqIX2xUKl1MeUtFA8g3DhVPA+xuZ2Cg6UH6Ho6Ak+Lbl+MoYFfkD+5ajYvpkoyzthZ5ZfUGVGoBgni3s1s0tUKcpagOInwMOLj3/GFbUcXbegcjZMeBimfsX41PRzMMvQ4n9a9je4i4gXiTQ1g0uDhxy0wsyVdF8Oqhqa0VdP2NV0xSZtdEhPw6ryxISuGqLlyZ9jYC5AMTkU3kFJcvcEw/t7l6U1NXjq5laadpqTI5r3pXkfbyAuRb+8COfkvCQy8tb/Cn7C1J+2ASFOt6+NDb8yVEAvkjgFTGno2bRAdkvqNtCNQhC+K4BWpMRtjyZTETrCxrXE1azt5dTbU5nKQb6Gp9zQ7Q4xhvY9uGbkrzJLejvlmonDF+yoUVq2nHB8Mfzaza3Zn2P5FIxKV6JY/yQgGooLP8Yr/IXJZkCiWeWE/QU2ocjvWfCVX5Po5n5/LRXFqi1KU2tTcd5iuV8FydcYblgrNNgkyAHnAsQfZ8QJ0MmO7Uko43ONFLoZs=
Signature digestsha512
Dependencies
Required Nextcloud versions >=34.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.3.0
LLM Chat 2.4.0
Release Details
UpdatedSept. 5, 2026, 6:43 a.m.
Changelog

Added

  • New tool "Ask you a question" (issue #15), off by default. A model that is genuinely unsure what was meant can now ask instead of guessing, and gets the answer back as a tool result rather than having to end its turn on a question the user then has to answer as a new message. Up to five questions in one call — asking again after the first round is answered is far more annoying than one longer form, so the cap is on the call, not on the dialog. Questions can carry options, which the user can pick from or ignore in favour of typing; a free text field is always there, because the model guessed the choices and being forced into the closest wrong one is worse than typing — where the options are checkboxes the typed answer is added to what was ticked rather than replacing it, since "all that apply" and "one of these" promise different things. Answering only some of them is allowed: a model asking five questions has usually asked one too many. Dismissing the dialog tells the model so in as many words, with an instruction not to ask again but to name the assumption it went with instead. Two such calls per answer, counted across the whole agent loop rather than per round — the five-question cap sits in the tool definition, so without this a seven-round budget would allow asking in every one of them. Counted only when a dialog actually opens, so a malformed call the model can still fix does not cost it an attempt, and a call that finds the budget spent is told exactly that instead of being reported as a dismissal nobody performed.
  • The composer has a file picker (issue #16). The file tools address paths relative to the home, and typing one by hand is tedious enough that people instead describe the file and hope the model finds it — which costs a search, a listing, and often the wrong file. Picked paths are inserted as plain quoted strings rather than markdown links or attachment chips: that is exactly what nc_read_text, nc_read_pdf and nc_read_image take as their path argument, so nothing has to be translated back. Multi-select and directories are allowed, the picker reopens where the last pick happened, and the button only appears for profiles that have the Nextcloud tools — a path in the prompt is noise to a profile that cannot open it. Nextcloud permits " in filenames, so the quote character steps aside instead of being escaped — single quotes, or a markdown code fence when the name contains both kinds. Backslash-escaping would fix the parse and create a worse bug: the model would have to strip the escapes again before calling a tool, and one that does not asks the file service for a file that does not exist under that name.

Fixed

  • nc_read_image scales images down before they go out (issue #14). Until now whatever sat in Nextcloud went to the model untouched, so an 8 MB phone photo became ~10.7 MB of base64 in the request body — the PDF path, which has to render an image first, was the thriftier of the two. Tokens were never the issue: Anthropic and OpenAI resize server-side anyway and bill for what comes out of that. What was the issue is that Anthropic refuses anything over 5 MB per image with an HTTP 400 that arrives after the tool ran and the user approved it, taking the whole turn with it. Images now go through the same treatment as a rendered PDF page — 1568 px on the longer edge, JPEG q0.85 — with EXIF orientation applied while decoding, since a portrait photo otherwise reaches the model lying on its side, and transparency flattened onto white, since JPEG has no alpha and undefined pixels come out black.
  • An image that is already small in both senses — at most 1568 px and under 1 MB — is passed through untouched: a re-encode would only make it blurrier. Same for SVG, which is markup a canvas cannot be pointed at, and for a flat graphic that happens to encode larger as JPEG than it was as PNG. Every one of those paths goes through the same size check and refuses with a message the model can relay, rather than handing back an oversized original — a passthrough that skips the ceiling is the exact HTTP 400 this change exists to prevent.
  • Animated images are resized like everything else rather than passed through to preserve the animation. No provider looks at more than one frame — OpenAI documents GIF support as non-animated, Anthropic takes a single frame — so the remaining frames are bytes paid for and never read, and an 8 MB animation refused for being over the limit is strictly worse than its first frame at 200 KB. Small ones keep their animation anyway by falling into the size passthrough, where it costs nothing either way.
Licenses GNU Affero General Public License v3.0 or later
Certificate-----BEGIN CERTIFICATE-----
MIIEAjCCAuoCAhP6MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwOTA0MTMxNTExWhcNMzYxMjEwMTMxNTExWjASMRAwDgYD
VQQDDAdsbG1jaGF0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7HDs
jKp7FsGKcuel3l3CTv8nfsIZXOMCyn2/kR0cZ18lnjrsxstIV/kB+t/lHD/V63Sp
weVFyxdOR5suH1ZYyc2iAGNtghJOq/hijKwSCNeJfSijyaHPOThIQaNDF30hDrSe
zThc3KidVfRfLz+FDJl18KzUJvYYRBDrBpd70UuDuiVoDu2mNPsUMx/ubQzLCCA+
lGOcWMAV8wcBi3SE/CNciqDvzU1WAIxl0KcUzCdF924IyG7XcRktuSdDXxMEPiV4
r3FYkUVZLTMkjT6gOzWNGt0i5pjJNFV0WYo3+OnaT3Ra2tCxSFYPGUhptxsY6xTu
gLOkSc5SOT8CSsFS7fjERutvt8C0oHEbu5N7TDoywBIcgu+oSe6pGuI12D8WAt+C
EuLs1OM4i7yUETtYoeURPWXDyhhu58Lfl89EFOfKjwhuySDhX6ALV8d559hPD9VY
OU/zv6j6De0m89lYgWXyoF0ClT0iu5OXqFO5NQuW8ZnXN2rZVvVNapMuYXSWdeL/
qi/xzivNsS++fZrRAO8kfGViN8wrcPcw4laKw/82iTsI9rldsimXkMQx2rXDw+4/
dCor1rdv3pAKv0xODI6bvLO3/df5DjlDHqAl9H++E2SiXciII+4abLQ+1lP8Ebsi
4e9lVt6ypI8Q3AeQunGWIiFmCtmKmM22jvgMTEsCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAffSGNpUST/kNZvVzcELBSu7RY77qo2MmkJ+zgo+4raXq66G3EITIIHyA
vbx2Mrok18l+xLNhd+m37+1W31/HRdKIsldma2qfb1eVw9bDH927LdHjFPKcZuIu
d6o3y6DS+jcYC01ghT9eDo4ammi5xUDJrriDkA1FAQl8VDhofCe76U40GSZTlg8f
uON0WiFfRuA7IG8Trw2DcQFEHEi7ufVEV/OUFdQoDAtIljunOm4iXV9pRLdj1Um6
UYKKERhs3/oIUjuFSbuBQgUBo7LCt5q69K64hfwexNax/pEkIR8kjzdtnUdqcAhX
3nq8ZcjtzKgN3LRYfYUEgRYAyEqthQ==
-----END CERTIFICATE-----
SignatureGXXp5Wmnvq/CoAIe03yQrPaNWS7jQ81KGL+mU/2wfhS3Y75VmjDl1EBDmH2U7G38YTKcM6XTbvRQE3zH6n8j7z356u6Uf7ilX9eAv9RyG/KHYy8xogGdauQkz5LaK6aI1e2XX0xM6vg5akuxq+hngD1ynAbnIZ0lRyQOVYjz18fTxgs8rY8OCTajOOVeaDkuLc9PAzeS7K9k+g7ZgvNxEDEwI/akor07YKdSp8WPApXAwyB/mmSBAxc9qwyUnNf18bczS6ah5e/rD7HnFhAY4K4kB698uk8FjdbWTbsyZy6dzz+DN5l9MDcsIMjOP5xD83I/thc7Wu+EHxERtxqACebWfkVUBdX7clklM+N1V1j/600InPmwugAU0MkSdl9qHQ4KUhy9OW/CKWrsPYClTufjzF+ztjN3V2av2VmyKgCZmh2d/npz1CugCDTqqASwmFTVQ81aKbXq4UvQ1PDfJcS61Gj9rE0+2373OSj5hc0835KlLxShKnMpoe9MrJwSXrNN8Si+RmwOAPY+/+vmTuVwX8YKrwIJhu5QTMT6C2UAK1wMjq3cFaQU34yT0MreTx6QTEvjFID8TbD4YfSAQDP2bJKM6lm0huBlvV5zdx5BsMpyv6mlfcG/sFBw1XgKEZmQwj8BKLskmp0iSYghuZjt4tT+gnYfTI+9LrHdcxo=
Signature digestsha512
Dependencies
Required Nextcloud versions >=34.0.0,<35.0.0
Minimum Integer bits32
PHP>=8.2.0