Commit Graph

13995 Commits

Author SHA1 Message Date
renovate[bot] e0fa2bbed4 chore(deps): update dependency vue-tsc to v3.3.3 2026-05-30 13:17:09 +00:00
kolaente 9456223556 fix: prevent package postinstall hang when generating jwt secret
The postinstall scripts generated the jwt secret with:

  cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1

This relies on SIGPIPE to terminate the infinite `cat /dev/urandom`
once `head` has read its single line. Inside a dpkg/apt maintainer-script
context the SIGPIPE disposition is not reliably delivered, so
`cat /dev/urandom` spins forever, the postinstall never returns, and the
whole `dpkg -i` / upgrade hangs.

Read a bounded 512 bytes with `head -c` instead so nothing depends on
SIGPIPE to terminate. 512 random bytes yield ~124 alphanumerics on
average, so the trailing `head -c 32` reliably produces a full 32-char
secret while staying dependency-free.

Fixes #2660
2026-05-30 12:39:49 +00:00
dependabot[bot] f7921238e6 chore(deps): bump axios from 1.15.2 to 1.16.0 in /frontend
Bumps [axios](https://github.com/axios/axios) from 1.15.2 to 1.16.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.15.2...v1.16.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.16.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-30 08:48:43 +00:00
Rémi Lapeyre 069685f2a7
fix(caldav): return 404 when trying to access a project that cannot exist with CalDAV (#2796) 2026-05-28 08:14:52 +02:00
Frederick [Bot] 50bece8cdb chore(i18n): update translations via Crowdin 2026-05-28 02:06:56 +00:00
renovate[bot] 7d1372ece3 chore(deps): update dev-dependencies 2026-05-27 21:18:08 +00:00
kolaente 2395239f0b
fix(ci): generate config.yml.sample in release-os-package for vikunja
vikunja's nfpm.yaml packs ./config.yml.sample as /etc/vikunja/config.yml.
The release-binaries action already regenerates it for the zip bundles,
but release-os-package runs on a fresh runner without that file, so
nfpm aborted with "matching ./config.yml.sample: file does not exist"
on every vikunja os-package matrix shard (the veans shards skip this
step entirely).

Add a vikunja-only step to regenerate it before nfpm runs.
2026-05-27 18:16:38 +02:00
kolaente 1e1fcaafbc
fix(ci): drop "./" from PACKAGE_OUTPUT_DIR so strip-path-prefix matches
The s3-action expands the upload glob into paths without a leading
"./", but strip-path-prefix was set to "./dist/os-packages/" (or
"./veans/dist/os-packages/"). The prefix never matched, so packages
landed at /<project>/<version>/<project>/dist/os-packages/<file>
instead of /<project>/<version>/<file>.

Drop the "./" prefix to match the working DIST_PREFIX pattern in
release-binaries.
2026-05-27 17:35:58 +02:00
kolaente 4f8a44de89
fix(ci): switch release composite actions to unstable on non-tag builds
The release-binaries and release-os-package composite actions were
comparing the raw release-version input against the literal "main" to
decide whether to use "unstable" for filenames and the S3 directory.
Callers always pass `steps.ghd.outputs.describe`, which is a value
like `v2.3.0-408-ge053d317` on non-tag builds — so the check never
fired and unstable artifacts landed under `/<project>/<describe>/`
with `<project>-<describe>-...` filenames.

Drive the switch from `github.ref_type == 'tag'` instead, matching the
pattern the desktop and config-yaml jobs in release.yml already use.
The raw describe value still flows into RELEASE_VERSION so the binary
and package metadata keep the precise commit reference.
2026-05-27 17:32:47 +02:00
kolaente e053d3172f
fix(ci): escape ${{ secrets.* }} mention in release-binaries description
GitHub's action manifest parser evaluates `${{ ... }}` expressions inside
`description:` block scalars, and `secrets` isn't a valid context in a
composite action — so the literal example text in the docstring caused
manifest validation to fail before any step ran.
2026-05-27 17:02:56 +02:00
kolaente be7eabb9b3 ci: move build-mage prep job out of test.yml into release.yml
build_mage_bin is only consumed by publish-repos in release.yml, so it
doesn't belong in the test workflow. Move it to release.yml as a
prep job and add it to publish-repos's needs list.
2026-05-27 13:01:44 +00:00
kolaente ed9df9064c refactor(ci): derive composite-action inputs from project name
Reviewer asked us to stop over-configuring the release-binaries and
release-os-package composite actions — they're called only with
vikunja or veans, so per-project paths, artifact names, cache keys, S3
target, and version-or-unstable can all be derived inside the action
from the project name. The xgo-out-name input goes away too.

Vikunja-specific pre-build (downloading frontend_dist, generating
config.yml.sample) now happens inside the action, gated on the project
input. Callers no longer need those preamble steps.

Secrets stay as inputs — composite actions can't read \`\${{ secrets.* }}\`
directly; passing them through is the simplest workaround.

Each callsite shrinks to ~13 lines of mostly-secret pass-through plus
2-4 lines of real parameters.
2026-05-27 13:01:44 +00:00
kolaente 304fe55da7 refactor: drop Release.* from project magefiles and point Dockerfile at build/
The release pipeline lives entirely in build/magefile.go now, so the
per-project Release namespaces in vikunja's magefile.go and
veans/magefile.go are dead weight. Drop them.

Update the Dockerfile in the same commit so the apibuilder stage
invokes `cd build && mage release:xgo vikunja <target>` — the parent
magefile no longer exposes that target.
2026-05-27 13:01:44 +00:00
kolaente e903b72b9e refactor(ci): call release composite actions from release.yml
Replace the inline bodies of binaries, veans-binaries, os-package, and
veans-os-package jobs with calls to the new release-binaries and
release-os-package composite actions. Each call site is now ~25 lines
of inputs instead of ~75 lines of duplicated mage+upx+gpg+s3 plumbing.

publish-repos switches from the parent's ./mage-static to the
prebuilt build_mage_bin artifact so it can drive build/'s repo metadata
targets inside the publish-repos containers.
2026-05-27 13:01:44 +00:00
kolaente 95f65c4712 ci: prebuild static build/mage for repo metadata containers
publish-repos runs inside ubuntu/fedora/archlinux containers that don't
ship a Go toolchain. Compile build/magefile.go into a static binary in
the test workflow (mirroring the existing mage_bin job for the parent
magefile) and upload it as the build_mage_bin artifact so publish-repos
can chmod+x and run it without setup-go.
2026-05-27 13:01:44 +00:00
kolaente c690f74d75 feat(ci): add release-binaries and release-os-package composite actions
Two reusable composite actions wrap the CI side of the release pipeline:

- release-binaries: setup-go, install mage + upx, cache xgo, invoke
  `mage release:build <project>` from build/, GPG-sign the zip bundles,
  upload to S3, store binaries and zips as workflow artifacts.

- release-os-package: download a binaries artifact, install mage,
  `mage release:prepare-nfpm-config <project> <arch>`, stage the binary,
  nfpm pack (with rpm signing inline and archlinux signing after), upload
  to S3, store the package as an artifact.

Both actions are parameterized on project name, output paths, artifact
names, S3 target, and GPG/S3 secrets — adding a third Go binary to the
monorepo just means defining its project in build/magefile.go and adding
a four-line call site in release.yml.
2026-05-27 13:01:44 +00:00
kolaente 8313d032ea feat(build): add centralized release magefile module
New build/ Go module hosts the full release pipeline (xgo cross-compile,
upx, sha256, zip bundles, nfpm templating, deb/rpm/apk repo metadata)
for every Go binary in the monorepo. Parametric on project name —
`mage release:build vikunja` and `mage release:build veans` both flow
through the same code.

The module is intentionally self-contained: it depends on nothing but
stdlib + mage, and duplicates the small filesystem helpers (copyFile,
moveFile, sha256File) rather than importing them from a project
magefile. That keeps the release tooling free to evolve without
touching project code.
2026-05-27 13:01:44 +00:00
kolaente f39cf00290 ci: register custom actionlint runner labels
Declare namespace-profile-default and blacksmith-8vcpu-ubuntu-2204 as
known self-hosted runners so actionlint stops flagging them as unknown.
2026-05-27 13:01:44 +00:00
kolaente 5f00fca166 feat(veans): build and publish veans alongside vikunja
Cross-compile veans for the same OS/arch matrix as the main vikunja
binary, wrap each into a signed zip, build deb/rpm/apk/archlinux
packages via nfpm, and merge those into the existing dl.vikunja.io
package repos so `apt install veans` works from the same source.

- veans/magefile.go: Release namespace (xgo cross-compile, upx, sha256,
  per-target zip bundle, nfpm.yaml templating).
- veans/nfpm.yaml: minimal — binary at /usr/local/bin/veans, no service
  or postinstall.
- .github/workflows/release.yml: veans-binaries + veans-os-package
  jobs, veans artifacts merged into publish-repos and create-release.
S3 layout mirrors vikunja under /veans/<version>/.
2026-05-27 13:01:44 +00:00
kolaente d5ab54941f
fix(deps): bump ip-address to >=10.1.1 in desktop workspace
Resolves a medium-severity XSS in Address6 HTML-emitting methods
(GHSA / Dependabot alert #224). Vulnerable range: <=10.1.0,
patched in 10.1.1. The package is pulled in transitively through
socks -> socks-proxy-agent in the Electron build chain
(devDependency only), but we add a pnpm override to ensure the
patched version is used everywhere. The frontend workspace already
has the equivalent override.
2026-05-27 11:10:24 +02:00
kolaente e08f05119d
fix(deps): bump qs to 6.15.2 in desktop
Resolves Dependabot alert #233: qs.stringify crashes with TypeError on
null/undefined entries in comma-format arrays when encodeValuesOnly is
set (DoS, medium severity).

Updates transitive dependency via pnpm update from 6.15.0 to 6.15.2.
2026-05-27 11:09:27 +02:00
kolaente 7be5026113
fix(deps): bump tmp to >=0.2.6 to fix path traversal vulnerability
Adds a pnpm override for `tmp` in both the `frontend` and `desktop`
workspaces to force the patched version (0.2.6). The previous transitive
resolutions (`tmp@0.0.33` via external-editor in frontend, `tmp@0.2.3`
via tmp-promise in desktop) are vulnerable to a path traversal via
unsanitized prefix/postfix that enables directory escape.

Addresses Dependabot alerts #234 (desktop) and #235 (frontend).
2026-05-27 11:09:20 +02:00
Tink bot 98affb265a test(veans): cover bootstrap validators and prompt UX
Three helpers I added recently have no e2e coverage because the
suite always passes --bot-username with a valid name and
--yes-buckets to skip prompts.

Nine tests in a new bootstrap_test.go:

- TestValidateBotUsername — table-driven, 18 rows: valid shapes
  (bot-foo, bot-foo-bar, bot-foo123, bot-foo_bar, bot-foo.bar,
  bot-a), invalid prefix (foo, Bot-foo, ""), invalid chars
  (spaces, commas, uppercase, !, embedded space), the reserved
  link-share-N pattern, and the bare "bot-" edge.

- TestConfirmOverwriteExistingConfig — file-missing path, the
  OverwriteExistingConfig=true short-circuit, every interesting
  prompt answer (y, yes, Y, Yes, "  yes  " → proceed; n, "",
  garbage → CodeConflict with path in message; prompter error
  → CodeUnknown wrapping the original via errors.Is).

- TestBootstrapBuckets_{AllPresent,AutoApprove,PromptDeclined,
  PromptAborted,PromptUnknownCap,PromptAccepted} — drive the
  function against a stub httptest server (bucketServer helper)
  that records ListBuckets responses and CreateBucket payloads,
  with a scripted queuePrompter for the prompt-driven cases.
  Covers the alias-match short circuit, the auto-approve path,
  the new declined/aborted/retry-cap paths, and the y-accepted
  path.

Local helpers (queuePrompter for scripted answers with injectable
error; bucketServer for the stubbed bucket endpoints) stay in the
test file — no production code changes.
2026-05-27 08:21:57 +00:00
Tink bot 964fdb71d1 test(veans): cover OAuth callback handler error paths
The e2e suite bypasses the OAuth flow via --token, so the callback
handler's error branches had zero coverage. Eight tests appended to
oauth_test.go drive the handler directly:

- happy path: code+state arrive on the channel; response is HTML
- authz-server error path: ?error=access_denied&error_description=…
  bubbles up as a non-nil err containing the description (not the code)
- only-code fallback: when error_description is missing, the error
  message falls back to the error code
- empty code: handler captures it; waitForCallback's job to reject
- non-GET method: 405 with Allow: GET, nothing pushed to channel
  (defense against forged POST from a same-origin page)
- wrong path: 404, nothing pushed
- HTML-escaping: an error containing <script>…</script> renders as
  &lt;script&gt; — XSS regression guard
- nil-err success page: 200 with 'veans is authorized'

Plus generateState shape coverage (length, charset, uniqueness)
to match the existing TestGeneratePKCE_*.

Sanity-checked the XSS test by deleting the html.EscapeString call —
it fails with raw <script> in the body. Restored.
2026-05-27 08:21:57 +00:00
Tink bot 4cda019336 test(veans): cover client error mapping, pagination, and 404→BOT_USERS_UNAVAILABLE
internal/client/ had no coverage for the helpers that turn HTTP
responses into the stable error envelope. e2e exercises happy paths
but never asserts the envelope's Code field for each status, so a
refactor of mapHTTPError could silently drift.

Seven tests in client_test.go:

- TestMapHTTPError_StatusCodeMapping table-drives 401/403/404/409/
  429/400/422/500 → the right output.Code constants.
- TestMapHTTPError_RetryAfterAppendedToMessage asserts the
  (retry-after <dur>) suffix on 429s.
- TestMapHTTPError_BodyTruncation pumps 600 bytes and asserts the
  message ends with …(truncated) and Cause stays nil (per the earlier
  'drop synthetic Cause' change).
- TestMapHTTPError_VikunjaJSONTakesPrecedenceOverRawBody asserts the
  parsed {code,message} payload wins over the raw body for the
  embedded message text.
- TestParseRetryAfter handles delta-seconds, HTTP-date forms (with a
  tolerance window because the parser uses time.Until), and the
  unparseable/empty/negative/past-date cases.
- TestPaginationDone covers the header-authoritative and len-heuristic
  branches across full, short, and empty pages.
- TestCreateBotUser_404TranslatesToBotUsersUnavailable drives a
  fake httptest server returning 404 on PUT /api/v1/user/bots and
  asserts the error code is BOT_USERS_UNAVAILABLE (the translation
  lives in users.go:37-42).
2026-05-27 08:21:57 +00:00
Tink bot 9b95d05811 test(veans): cover the stable error-envelope contract
internal/output/ had zero unit tests; the envelope shape it produces
is consumed by every agent integration on the other side of stdin, so
locking it down with a small test file is high-leverage.

Six tests in errors_test.go:
- TestAsError_Nil, TestAsError_PreservesKnownCode,
  TestAsError_UnwrapsThroughFmtErrorf,
  TestAsError_PlainErrorBecomesUnknown — pin AsError's contract
  against nil / direct / wrapped / plain inputs.
- TestEmitError_EnvelopeShape — round-trips through bytes.Buffer and
  asserts exactly two keys ("code", "error"), correct values, and a
  trailing newline.
- TestWrap_PreservesCauseForErrorsIs — confirms errors.Is and
  errors.As walk through Wrap so future sentinel introductions work.

A comment block documents why EmitError's encode-failure fallback
isn't exercised — json.Marshal of {Code, Message} cannot fail, so the
branch is unreachable from outside the package.
2026-05-27 08:21:57 +00:00
Tink bot c715520ab9 test(veans): cover credential-store hardening invariants
Four unit tests in internal/credentials/file_test.go for behaviors that
have no e2e coverage (e2e exercises file-backend writes round-trip but
never stats the mode, never simulates a crash, never races two
processes, never observes the fallback warning):

- TestFileBackend_SetReassertsMode pre-creates the file at 0o644 and
  asserts Set narrows it to 0o600 via Chmod-after-Rename.
- TestFileBackend_SetCleansUpTmpFile scans the dir after Set and
  fails on any leftover .credentials-*.tmp.
- TestFileBackend_ConcurrentWritersSerialize runs two goroutines
  writing distinct keys; both records must survive (verifies the
  flock around load-mutate-save).
- TestChain_SetWarnsOnFallback captures ChainStderr via bytes.Buffer
  and asserts the one-line warning when a writable backend errors
  before the file backend succeeds.
2026-05-27 08:21:57 +00:00
Tink bot f04930137e test(veans): pin runUpdate's call-order invariants
The two ordering rules in commands/update.go::runUpdate aren't enforced
by anything beyond the lines being written in that sequence:

  1. MoveTaskToBucket runs AFTER UpdateTask, so a status transition
     doesn't clobber freshly attached labels.
  2. The scrapped-reason comment posts BEFORE the bucket move, so the
     audit trail reads chronologically.

Both are documented in CLAUDE.md but neither is exercised by the e2e
suite: TestUpdate_DescriptionReplaceUniqueness is the only update-side
e2e and it only covers --description-replace-old/new.

Add two unit tests that drive runUpdate against an httptest.Server and
assert the exact (method, path) sequence. Sanity-checked locally by
swapping the field-update and bucket-move blocks — both tests fail with
a clear order diff, confirming they catch the regression that's most
likely to slip through review.
2026-05-27 08:21:57 +00:00
Tink bot ba6615f378 feat(veans): warn when Chain.Set falls back past a failed backend
A keyring transient failure on Set silently falls through to the file
backend today, which leaves a stale keyring entry from any prior
successful write shadowing the new file-backend token. Fixing the
shadow itself is deferred (would need a Set-and-Delete coordination,
or a stricter contract).

What we can do cheaply: surface the fallback so an operator hitting
the shadow has a breadcrumb. On Chain.Set fallthrough past a writable
backend that errored, print:

  veans: credential store: keyring rejected write (X); falling back to file

The warning goes to stderr (not the structured envelope — Set still
returns nil because the write landed somewhere). Env-backend's
read-only skip is unchanged and silent.

ChainStderr is exposed as a package var so tests can capture/assert
the warning when we backfill credential-store coverage.
2026-05-27 08:21:57 +00:00
Tink bot 75e546f0c1 feat(veans): make the HTTP client timeout configurable via .veans.yml
The 30s timeout on the client.New HTTPClient was hard-coded and
opaque. Long-running paginated reads against slow networks were
tripping it with no escape hatch.

Lift the value into a named constant and let .veans.yml override it
via a new optional http_timeout field (Go duration syntax, e.g.
"60s", "5m"). The field has omitempty so a freshly-written
.veans.yml from `veans init` doesn't surface the knob — operators
who need to tune it can hand-edit, but it stays out of the way for
the common case.

Runtime loader applies the override after client.New if set;
bootstrap- and login-time clients (built before .veans.yml exists)
keep the default.
2026-05-27 08:21:57 +00:00
Tink bot c4a0575305 feat(veans): offer "create a new project" from init's picker
The project picker used to require at least one pre-existing project
and would otherwise hard-error: "no projects visible to this user —
create one in the Vikunja UI first". Now it always offers an extra
numbered entry "Create a new project" and, when the user picks it,
prompts for a title (required) + identifier (optional). Empty-list
case routes straight to creation.

Backed by a new client.CreateProject(ctx, *Project) method (`PUT
/projects`); the e2e harness now uses that instead of the raw c.Do
call it did before.

Also fixed a latent bufio bug in StdPrompter.ReadLine that this work
surfaced: every call created a fresh bufio.Reader, which read-ahead a
buffer and threw it away on return. Second+ prompts read empty. Reuse
one buffered reader on the StdPrompter instance.
2026-05-27 08:21:57 +00:00
Tink bot 9b8ad4d027 feat(veans): URL discovery on init, port of the frontend's heuristic
The previous init flow took whatever the user typed for --server and
called GET <url>/api/v1/info on it. If the user typed
"vikunja.example.com" (no scheme), or pasted the URL with /api/v1 in
it (double-suffix), or pointed at a localhost install on the default
:3456 port without typing the port, we'd hand back a raw HTTP error.

New `client.DiscoverServer` ports the frontend's
helpers/checkAndSetApiUrl.ts discovery: probe a small ordered set of
plausible bases for /api/v1/info, return the first one that returns
parseable Info. Candidate order:

  1. scheme://host[:port]/path           (as the user typed it)
  2. scheme://host:3456/path             (default API port)
  3. opposite scheme of (1)
  4. opposite scheme of (2)

Heuristics:
- Missing scheme → https for public hosts, http for localhost /
  127.0.0.1 / [::1] (matches most CLIs' behaviour)
- Trailing /api/v1 from a pasted URL is stripped before probing, so
  we don't double up to /api/v1/api/v1/info
- Trailing slashes normalized

Errors now list everything we tried + the last underlying network
error, so the user can see why a URL failed instead of just
"GET /info: connection refused":

  veans: VALIDATION_ERROR: couldn't find a Vikunja instance reachable
    from "vikunja.example.com" — tried:
    - https://vikunja.example.com/api/v1/info
    - https://vikunja.example.com:3456/api/v1/info
    - http://vikunja.example.com/api/v1/info
    - http://vikunja.example.com:3456/api/v1/info
    last error: dial tcp: lookup vikunja.example.com: no such host

bootstrap.Init now defers URL canonicalisation to DiscoverServer and
caches the matched info from the probe (no second /info round-trip).

Unit tests cover the candidate-builder across the common shapes:
bare hostname, localhost, /api/v1-suffixed paste, explicit port,
subpath install, 127.0.0.1:3456, trailing slash. e2e green.
2026-05-27 08:21:57 +00:00
Tink bot 814b2a635f feat(veans): install agent hooks during init instead of just printing
Adds a final step to bootstrap.Init that offers to wire `veans prime`
into Claude Code and OpenCode automatically. Per-agent yes/no prompts
default to "yes" for Claude Code and "no" for OpenCode; --install-claude
/ --install-opencode flags skip the prompt for scripted contexts;
--no-hooks falls back to the previous behaviour of just printing the
snippets.

Claude Code:
  - Writes/merges .claude/settings.json
  - JSON merge preserves existing keys (model, permissions, other hooks)
    and only appends a `veans prime` command entry under SessionStart
    and PreCompact if one isn't already there
  - Idempotent: re-running reports "Already configured" without
    duplicating entries

OpenCode:
  - Writes .opencode/plugin/veans-prime.ts with the standard handler
    skeleton
  - Existing files are left alone (no TS-merge story for v0)

Failures during hook install are non-fatal: the repo is already
configured, so the user gets a warning + the printed snippets as a
fallback path.

Unit tests cover the merge logic (fresh file, idempotent rerun,
preserving user's other hooks/keys), the install actions
("Wrote"/"Updated"/"Already configured"), and the offer flow
(flags-bypass-prompt vs prompt-when-unset vs no-hooks).
2026-05-27 08:21:57 +00:00
Tink bot 1bc3afa430 feat(veans): match existing bucket titles via case-insensitive alias table 2026-05-27 08:21:57 +00:00
Tink bot 4ac89741e3 feat(veans): reuse owned bot or prompt for fresh name on collision 2026-05-27 08:21:57 +00:00
Tink bot cd7cc113a1 docs(veans): AGENTS.md cheat sheet for coding agents
Captures the non-obvious things an agent will hit working on this
submodule:

- Wire-format quirks (view_kind/bucket_configuration_mode are JSON
  strings; Task.BucketID is always 0 in GET — use ?expand=buckets and
  CurrentBucketID; POST /tasks doesn't move buckets, use the dedicated
  bucket-tasks endpoint; bot creation is at /user/bots; APIToken
  expires_at is required, use FarFuture for "no expiry").
- Permission discovery via /routes (group names are path-derived; use
  PermissionsForBot at runtime instead of hard-coding).
- OAuth shape (PKCE/S256 mandatory, no client registration, JSON-only
  token exchange, loopback redirect via 127.0.0.1:0, Shutdown uses
  context.WithoutCancel to drain on outer cancel).
- Credential chain order + per-test HOME/XDG override.
- Identifier validation (runelength only) + base-36 timestamp suffix
  trick for unique e2e identifiers.
- mage Aliases map (without it, `mage test` rejects the namespace).
- License-header enforcement via local .golangci.yml + code-header-
  template.txt copy.
- Things to actively avoid: bare exec.Command, committing the built
  binary, stdout from `prime` outside a configured workspace.

CLAUDE.md is a symlink to AGENTS.md so Claude Code picks it up via
either name.
2026-05-27 08:21:57 +00:00
Tink bot 632579b304 ci(veans): add fast veans-test job for unit tests 2026-05-27 08:21:57 +00:00
Tink bot c1d5272afe feat(veans): switch OAuth handshake to a loopback HTTP server 2026-05-27 08:21:57 +00:00
Tink bot b18762171d feat(veans): add browser launcher helpers for OAuth flow 2026-05-27 08:21:57 +00:00
Tink bot 952ad89a8b chore(veans): apply veans golangci pass across sources 2026-05-27 08:21:57 +00:00
Tink bot 202a5f60b0 ci(veans): add veans-lint job to Test workflow 2026-05-27 08:21:57 +00:00
Tink bot 8ef796f016 chore(veans): add veans-local golangci config 2026-05-27 08:21:57 +00:00
Tink bot 35aa486eb5 feat(veans): use OAuth 2.0 Authorization Code + PKCE as default auth
Vikunja's built-in OAuth server (Vikunja 2.3+) does not require client
registration and accepts arbitrary client_ids — it just enforces PKCE
(S256) and constrains redirect URIs to the vikunja- scheme. Earlier I
deferred OAuth on the assumption it needed a registered client; that
was wrong, and the docs make the path much smoother than POST /login.

The custom-scheme constraint (no http:// loopback) is side-stepped by
manual paste-back: veans prints the authorize URL, the user signs in,
their browser fails to open vikunja-veans-cli://callback?code=... and
shows an error, the user copies the URL from the address bar and
pastes it back. CLI extracts code + state, verifies state for CSRF,
exchanges via POST /api/v1/oauth/token (JSON body — Vikunja rejects
form-encoded), and returns the access token.

Resolution order in AcquireHumanToken:
  1. --token (paste-in JWT or personal API token; SSO/OIDC users)
  2. --use-password / --username + --password (POST /login)
  3. OAuth flow (interactive default)

login command supports the same --use-password / --token escape hatches
for token rotation on instances with OAuth disabled.

Includes unit tests for the PKCE generator (verifier shape per RFC 7636,
challenge = SHA256(verifier) base64url-no-pad), authorize-URL
construction, and the lenient callback parser (full URL / query-only /
bare code).
2026-05-27 08:21:57 +00:00
Tink bot d0c77ad6fe docs(veans): add README with quick-start guide 2026-05-27 08:21:57 +00:00
Tink bot 950d41df91 ci(veans): add veans-e2e workflow 2026-05-27 08:21:57 +00:00
Tink bot 4c3d449a35 test(veans): add e2e suite covering init, tasks, claim, prime flows 2026-05-27 08:21:57 +00:00
Tink bot 3a7bcb2a50 chore(veans): gitignore built binary 2026-05-27 08:21:57 +00:00
Tink bot df7a60d137 feat(veans): add login command for token rotation 2026-05-27 08:21:57 +00:00
Tink bot 2e2393121b feat(veans): add api passthrough command 2026-05-27 08:21:57 +00:00
Tink bot e8cdfcf023 feat(veans): add prime command for agent prompt injection 2026-05-27 08:21:57 +00:00