Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
1723f06
feat(deploy): Deploy staging through FlatRun
nfebe Jun 7, 2026
bbd34b5
feat(docs): Serve Swagger UI from a view outside production
nfebe Jun 7, 2026
52990bc
fix(ai): Require tenant scoping for SmartQL queries
nfebe Jun 7, 2026
f2b1f42
feat(ai): Let admins run system-wide AI chat queries
nfebe Jun 7, 2026
418d471
feat: Update plugin engine for quieter logs and cacheable discovery
nfebe Jun 7, 2026
4a713f9
docs: Document plugin engine logging settings in the env example
nfebe Jun 7, 2026
539d70b
fix: Autoconvert transactions not in budget currency
kofimokome Jun 10, 2026
8d60b09
fix: Account for currency difference in budget progress
kofimokome Jun 12, 2026
ef1e13a
test(stats): Add coverage for statistics endpoint correctness
nfebe Jun 15, 2026
485d8bb
fix(ci): Restore the test run by repointing the coverage action
nfebe Jun 16, 2026
9c713b2
feat(stats): Add section-scoped responses for progressive loading
nfebe Jun 16, 2026
cb32f87
refactor(stats): Assemble sections via dispatch to bound complexity
nfebe Jun 16, 2026
aef6146
docs(stats): Document the section parameter in the API spec
nfebe Jun 16, 2026
00d0ad9
feat(deploy): Publish a per-commit image on merge and latest on release
nfebe Jun 16, 2026
1cbbe7e
refactor(deploy): Separate image publishing from staging deployment
nfebe Jun 16, 2026
7013fac
fix(deploy): Use short commit hashes and a valid FlatRun action ref
nfebe Jun 16, 2026
1d81509
feat(ai): Add assistant agent with confirm-before-write actions
nfebe Jun 17, 2026
696eaee
feat(ai): Analyze attached documents into reviewable imports
nfebe Jun 17, 2026
d934df2
feat(ai): Render and update canvas reports with export
nfebe Jun 17, 2026
f4a7591
refactor(transactions): Route writes through the shared writer
nfebe Jun 17, 2026
e655d4f
chore: Point SmartQL at the provider-native API key
nfebe Jun 17, 2026
3f6e734
refactor(ai): Scope proposed actions to a polymorphic owner
nfebe Jun 17, 2026
f5a6043
feat(ai): Expand assistant actions and harden chat reliability
nfebe Jun 18, 2026
ad984b5
fix: Report the current app version in the API docs
nfebe Jun 18, 2026
955aeee
fix: API rate limiter keys by user ID instead of IP for authenticated…
Lantum-Brendan Jun 19, 2026
b0ec806
fix(budget): Correct currency exchange rate conversion direction
kofimokome Jun 20, 2026
540f60a
test: Cover party stats currency conversion and partial flag
nfebe Jun 21, 2026
42d375b
feat(core): Add entitlements seam for feature, limit and usage gating
nfebe Jun 21, 2026
ce91abf
feat(webservice): Add hosted image build workflow
nfebe Jun 21, 2026
6cc6258
feat: Add priority to document processor selection
nfebe Jun 21, 2026
c77a325
feat: Add integrations registry and listing endpoint
nfebe Jun 21, 2026
27ae9c1
docs: Document plugin extension points and paid plugins
nfebe Jun 21, 2026
19b9343
fix(ci): Load bundled plugins through the engine, not Composer
nfebe Jun 22, 2026
195f426
feat: Add intent tagging, financial position, and asset holdings
nfebe Jun 27, 2026
50307c7
feat(user): Expose admin status in the user payload
nfebe Jun 27, 2026
e4e1448
feat: Add admin console with engagement metrics and outreach
nfebe Jun 27, 2026
7679d66
enh(outreach): Queue bulk sends and index datetime column
nfebe Jun 27, 2026
e3a1c5e
refactor(outreach): Back outreach with the eloquent-outreach package
nfebe Jun 28, 2026
247fc28
refactor(config): Rename country preference key to country
nfebe Jun 28, 2026
7f8f273
fix(budget): Correct currency conversion in budget spend
nfebe Jun 28, 2026
a4aefa8
feat(currency): Expose exchange rate and asset price endpoints
nfebe Jun 28, 2026
197d7c2
docs(currency): Document 401 response on rate and price endpoints
nfebe Jun 28, 2026
b84d445
feat(integrations): Let integrations describe their client UI
nfebe Jun 28, 2026
beb02e4
feat(plugins): Add CAMT.053 bank statement import plugin
nfebe Jun 28, 2026
ebbf077
fix: Return a consistent response envelope for holdings and stats
nfebe Jul 5, 2026
6cf518f
fix(import): Stop treating account numbers as transaction amounts
nfebe Jul 6, 2026
a765905
feat(import): Extract statements by reading the header, not fixed pos…
nfebe Jul 6, 2026
5571e1f
feat(import): Record statement fees as a linked charges expense
nfebe Jul 6, 2026
2cff775
feat(dev): Process the queue in the local docker stack
nfebe Jul 6, 2026
33e19d6
fix(ci): Bundle the prepared vendor tree into the hosted image
nfebe Jul 8, 2026
cfbc2d4
perf(stats): Serve transaction relations from loaded data
nfebe Jul 9, 2026
218ae08
perf(ai): Drop two model round-trips from every chat turn
nfebe Jul 9, 2026
32db7ba
feat(ai): Stream chat turns with live progress over websockets
nfebe Jul 9, 2026
4517779
chore(deps-dev): Bump laravel/pint from 1.29.1 to 1.29.3
dependabot[bot] Jul 1, 2026
f48172c
chore(deps): Bump guzzlehttp/guzzle from 7.10.0 to 7.14.0
dependabot[bot] Jul 11, 2026
975e837
chore(deps-dev): Bump phpstan/phpstan from 2.1.54 to 2.2.5
dependabot[bot] Jul 11, 2026
a206fb1
feat(mcp): Add core MCP server infrastructure
Lantum-Brendan Jun 22, 2026
3281919
fix(mcp): resolve rebase conflicts, test/lint failures, and PR review…
Lantum-Brendan Jun 30, 2026
582e673
fix(mcp): Add the MCP package cleanly and regenerate the lockfile
nfebe Jul 11, 2026
bd65069
refactor(mcp): Serve the MCP endpoint on the package transport
nfebe Jul 11, 2026
1f0fd98
feat(mcp): Expose finance tools over the MCP server
nfebe Jul 11, 2026
c7c67e3
feat(mcp): Let users issue access tokens for AI clients
nfebe Jul 11, 2026
cc08745
test(mcp): Cover the server and token endpoints, refine write gating
nfebe Jul 11, 2026
bb39372
fix(ai): Continue the assistant's plan after a confirmation
nfebe Jul 11, 2026
33197a3
feat(ai): Record agent token usage per user
nfebe Jul 11, 2026
69d24d7
refactor(mcp): Clean tool names and trim tool output
nfebe Jul 11, 2026
caf67bd
fix(mcp): Keep the token controller and stats tool within lint limits
nfebe Jul 11, 2026
9fe489b
docs: Document the tree-name release codename scheme
nfebe Jul 11, 2026
0b38168
fix(ai): Read a receipt as one transaction with its merchant
nfebe Jul 11, 2026
208be1a
fix(ai): Decline off-topic tasks disguised in financial wording
nfebe Jul 12, 2026
7855e5d
refactor(ai): Back proposed actions with the shared agent-actions ledger
nfebe Jul 12, 2026
c180d41
chore(release): Bump to v2.0.0-beta and record the changelog
nfebe Jul 11, 2026
a10eebe
chore(release): Number the prerelease as 2.0.0-beta.1
nfebe Jul 13, 2026
f966f5c
ci(commits): Adopt the shared commit-message validation action
nfebe Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

PLUGIN_ENGINE_LOG_CHANNEL=
PLUGIN_ENGINE_LOG_LEVEL=warning

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
Expand All @@ -16,6 +19,18 @@ DB_USERNAME=trakli
DB_PASSWORD=secret

BROADCAST_DRIVER=log

# Real-time chat streaming (Laravel Reverb). Set BROADCAST_DRIVER=reverb to enable.
# In the docker stack the app reaches the server as "reverb"; the browser uses
# the published host port (REVERB_HOST_PORT, default 6001).
REVERB_APP_ID=your-reverb-app-id
REVERB_APP_KEY=your-reverb-app-key
REVERB_APP_SECRET=generate-a-secure-random-string-here
REVERB_HOST=reverb
REVERB_PORT=8080
REVERB_SCHEME=http
REVERB_HOST_PORT=6001

CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
Expand All @@ -29,7 +44,7 @@ REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
Expand Down Expand Up @@ -88,3 +103,15 @@ GEMINI_API_KEY=
# MAILHOG_SMTP_PORT=1025
# MAILHOG_UI_PORT=8025
# SMARTQL_PORT=5000

# MCP (Model Context Protocol) Configuration
MCP_ENABLED=true
MCP_ENDPOINT=mcp
MCP_AUTH_GUARD=sanctum
MCP_RATE_LIMIT_ENABLED=true
MCP_RATE_LIMIT_MAX=60
MCP_RATE_LIMIT_DECAY=1
MCP_STRICT_PERMISSIONS=false

# Agent token-usage metrics (routes off; usage is read internally)
TOKEN_USAGE_REGISTER_ROUTES=false
12 changes: 12 additions & 0 deletions .env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,15 @@ MODEL_CONFIG_ROUTE_PREFIX=api/v1
# Firebase credentials path (ensure file exists in production)
FIREBASE_CREDENTIALS=storage/app/firebase-auth.json

# MCP (Model Context Protocol) Configuration
MCP_ENABLED=false
MCP_ENDPOINT=mcp
MCP_AUTH_GUARD=sanctum
MCP_RATE_LIMIT_ENABLED=true
MCP_RATE_LIMIT_MAX=60
MCP_RATE_LIMIT_DECAY=1
MCP_STRICT_PERMISSIONS=false


# Agent token-usage metrics (routes off; usage is read internally)
TOKEN_USAGE_REGISTER_ROUTES=false
54 changes: 54 additions & 0 deletions .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Build and Publish Docker Image

on:
push:
branches:
- dev
release:
types: [published]

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Resolve short commit SHA
id: meta
if: ${{ github.event_name == 'push' }}
run: echo "sha=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"

- name: Build and push commit image
if: ${{ github.event_name == 'push' }}
uses: docker/build-push-action@v4
with:
context: .
file: docker/prod/Dockerfile
push: true
tags: ghcr.io/trakli/webservice:${{ steps.meta.outputs.sha }}

- name: Build and push latest
if: ${{ github.event_name == 'release' }}
uses: docker/build-push-action@v4
with:
context: .
file: docker/prod/Dockerfile
push: true
tags: |
ghcr.io/trakli/webservice:latest
ghcr.io/trakli/webservice:${{ github.event.release.tag_name }}
137 changes: 137 additions & 0 deletions .github/workflows/build-hosted-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Build Hosted Image

on:
workflow_call:
inputs:
plugins:
description: 'JSON array of plugins to bundle, e.g. [{"repo":"<org>/<plugin>","ref":"main"}]'
type: string
required: true
base_image:
description: 'Public base image to stack on'
type: string
default: 'ghcr.io/trakli/webservice:latest'
image_name:
description: 'Target hosted image'
type: string
default: 'ghcr.io/trakli/webservice-hosted'
tag:
description: 'Tag for the hosted image'
type: string
default: 'latest'
secrets:
PLUGINS_TOKEN:
description: 'Token with read access to the private plugin repositories'
required: true
workflow_dispatch:
inputs:
plugins:
description: 'JSON array of plugins to bundle'
type: string
required: true
base_image:
type: string
default: 'ghcr.io/trakli/webservice:latest'
image_name:
type: string
default: 'ghcr.io/trakli/webservice-hosted'
tag:
type: string
default: 'latest'

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout webservice
uses: actions/checkout@v4

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer:v2

- name: Clone private plugins
env:
PLUGINS_TOKEN: ${{ secrets.PLUGINS_TOKEN }}
run: |
set -euo pipefail
mkdir -p plugins
while read -r plugin; do
repo=$(echo "$plugin" | jq -r '.repo')
ref=$(echo "$plugin" | jq -r '.ref // "main"')
tmp=$(mktemp -d ./plugins/.clone.XXXXXX)
git clone --depth 1 --branch "$ref" \
"https://x-access-token:${PLUGINS_TOKEN}@github.com/${repo}.git" "$tmp"
rm -rf "$tmp/.git"
if [ ! -f "$tmp/plugin.json" ]; then
echo "::error::${repo} has no plugin.json; it is not a plugin-engine plugin" >&2
exit 1
fi
id=$(jq -r '.id // empty' "$tmp/plugin.json")
if [ -z "$id" ]; then
echo "::error::${repo} plugin.json is missing an 'id'" >&2
exit 1
fi
rm -rf "plugins/${id}"
mv "$tmp" "plugins/${id}"
done < <(echo '${{ inputs.plugins }}' | jq -c '.[]')

- name: Install core dependencies
run: |
composer install --no-dev --no-interaction --no-scripts --ignore-platform-reqs --prefer-dist --optimize-autoloader

- name: Verify bundled plugins carry no unbundled dependencies
run: |
set -euo pipefail
for d in plugins/*/; do
cj="${d}composer.json"
[ -f "$cj" ] || continue
extra=$(jq -r '.require // {} | keys[]' "$cj" | grep -ivE '^(php|laravel/framework)$' || true)
if [ -n "$extra" ]; then
echo "::error::Plugin '${d}' declares dependencies the bundler does not install:" >&2
echo "$extra" >&2
echo "::error::Extend this workflow to install plugin dependencies before bundling it." >&2
exit 1
fi
done

- name: Prepare application environment
run: |
cp .env.example .env
php artisan key:generate

- name: Enable and cache bundled plugins
run: |
set -euo pipefail
for d in plugins/*/; do
[ -f "${d}plugin.json" ] || continue
php artisan plugin:enable "$(jq -r '.id' "${d}plugin.json")"
done
php artisan plugin:cache

- name: Keep prepared vendor and plugin cache in the build context
run: |
sed -i '/^vendor$/d; /^bootstrap\/cache/d' .dockerignore

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push hosted image
uses: docker/build-push-action@v6
with:
context: .
file: docker/hosted/Dockerfile
push: true
build-args: |
BASE_IMAGE=${{ inputs.base_image }}
tags: ${{ inputs.image_name }}:${{ inputs.tag }}
52 changes: 3 additions & 49 deletions .github/workflows/commits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,12 @@ name: Commit message validation

on:
pull_request:
branches: [main]
branches: [main, dev]
push:
branches: [main]
branches: [main, dev]

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: actions/setup-node@v3
with:
node-version: 18

- run: npm install --save-dev @commitlint/{config-conventional,cli}

- name: Create commitlint config file
run: |
cat << EOF > commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
'feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'build', 'ci',
'enh', 'enhance', 'tweak', 'imp', 'improve',
]],
'subject-case': [2, 'always', 'sentence-case'],
'header-max-length': [2, 'always', 72],
},
ignores: [
(commit) => commit.includes('Signed-off-by: dependabot[bot]')
]
};
EOF

- name: Run commitlint
run: npx commitlint --from HEAD^ --to HEAD --config commitlint.config.js

- name: Fail if commitlint fails (but not for Dependabot)
if: ${{ failure() && github.actor != 'dependabot[bot]'}}
run: |
echo "::error::Invalid commit message format. Please follow the conventional commit guidelines:"
echo "::error::- Type: One of: feat, fix, docs, style, refactor, test, chore, build, ci, enh, enhance, tweak, imp, improve"
echo "::error::- Scope (optional): A short name of the part of the codebase affected (e.g., 'auth', 'ui')."
echo "::error::- Subject: A brief description of the change, starting with a capital letter and generally in lowercase. Keep it concise (under 72 characters)."
echo "::error::- Body (optional): More detailed explanation of the change."
echo "::error::- Footer (optional): Related issue references, etc."
echo ""
echo "::error::Example:"
echo "::error::feat(user-authentication): Implement user login"
echo ""
echo "::error::For more information, see: [https://github.com/whilesmart/conventions/blob/main/commits.md]"
exit 1
- uses: whilesmart/workflows/commits@main
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ jobs:
- uses: actions/checkout@v6

- name: Update Coverage Baseline
uses: whilesmart/workflows/php/coverage/update@coverage
uses: whilesmart/workflows/php/coverage/update@main
with:
token: ${{ secrets.PAT_TOKEN }}
Loading
Loading