Commit Graph

26 Commits

Author SHA1 Message Date
devv-eve
6ef711cd35 fix: gate dev verification code behind explicit env (#1773)
* fix: gate dev verification code behind explicit env

* docs: fold dev verification code into env table

* docs: clarify fixed verification code opt-in

---------

Co-authored-by: Eve <eve@multica.ai>
2026-04-28 15:14:07 +08:00
devv-eve
f864a07bd5 feat: add server Prometheus metrics endpoint
Add Prometheus metrics endpoint with local-bind listener support and baseline metrics collectors.
2026-04-28 14:29:01 +08:00
LinYushen
99154d97b9 Restrict /health/realtime metrics exposure (MUL-1342) (#1608)
* Restrict /health/realtime metrics exposure (MUL-1342)

The realtime metrics endpoint was registered on the public router with
no authentication, exposing per-event/per-scope counters, redis.last_error,
and redis.node_id to anonymous callers. This enables information disclosure
and traffic profiling.

Move the handler behind a token + loopback policy:

- If REALTIME_METRICS_TOKEN is set, require Authorization: Bearer <token>
  using a constant-time compare. Reject other callers with 401 plus a
  WWW-Authenticate hint.
- If the env var is unset, only serve loopback callers and return 404 to
  remote clients so the endpoint is not enumerable. This keeps local dev
  workflows working without configuration.

The handler is extracted into health_realtime.go with focused unit tests
covering the token, loopback, and rejection paths. .env.example documents
the new variable.

Refs: https://github.com/multica-ai/multica/issues/1606

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fail closed for proxied /health/realtime requests (MUL-1342)

Addresses review on PR #1608: when the server runs behind a reverse
proxy (Caddy / Nginx -> localhost:8080), public callers reach the Go
handler with RemoteAddr=127.0.0.1, so the previous loopback shortcut
exposed the metrics surface in self-hosted deployments.

The no-token path now treats any forwarding header
(X-Forwarded-For / -Host / -Proto, X-Real-Ip, Forwarded) as a
'this request was proxied, can't attribute, fail closed' signal and
returns 404. Direct loopback callers without those headers still work
for local dev. Token-gated path is unchanged.

Tests cover all listed proxy headers (incl. multi-hop XFF chain and
RFC 7239 Forwarded) over both 127.0.0.1 and ::1, plus a regression
case ensuring an empty/whitespace forwarding header does not break
direct loopback access. .env.example updated to call out that proxied
deployments must configure REALTIME_METRICS_TOKEN.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: CC-Girl <cc-girl@multica.ai>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 14:04:10 +08:00
devv-eve
fbf41bde73 feat(selfhost): ship public GHCR deployment flow
Publish stable GHCR self-host images, switch self-host deploys to official image pulls with a source-build fallback, and move self-host signup / Google OAuth config onto runtime /api/config.
2026-04-22 16:58:42 +08:00
devv-eve
637bdc8eb3 feat(analytics): full PostHog pipeline + 6 funnel events (MUL-1122) (#1367)
* feat(analytics): add PostHog client with async batch shipping

Introduces server/internal/analytics, the shipping layer for the product
funnel defined in docs/analytics.md. Capture is non-blocking — events are
enqueued into a bounded channel and a background worker batches them to
PostHog's /batch/ endpoint. A broken backend drops events rather than
blocking request handlers.

Local dev and self-hosted instances run a noop client until the operator
sets POSTHOG_API_KEY. This is PR 1 of MUL-1122; signup and workspace_created
emission land in the follow-up commit so this change is independently
reviewable.

* feat(server): emit signup and workspace_created analytics events

Wires analytics.Client through handler.New and main, then emits the first
two funnel events:

- signup fires from findOrCreateUser (which now reports isNew), covering
  both the verification-code and Google OAuth entry points — a single
  emission site guarantees Google signups aren't missed.
- workspace_created fires after the CreateWorkspace transaction commits,
  with is_first_workspace computed from a post-commit ListWorkspaces count
  so we can distinguish fresh-user activation from returning-user
  expansion.

Tests use analytics.NoopClient so nothing ships from test runs. PR 1 of
MUL-1122; runtime_registered and issue_executed follow in later PRs per
the plan.

* refactor(analytics): drop is_first_workspace from workspace_created

Stamping "is this the user's first workspace?" at emit time races under
concurrent CreateWorkspace requests: two transactions committing close
together can both read a post-commit count greater than one and both emit
false. Fixing it at the SQL layer requires a schema change we don't want in
PR 1.

PostHog answers the same question exactly from the event stream (funnel on
"first time user does X" / cohort on $initial_event), so removing the
property loses no information and makes the emit side race-free.

* docs(analytics): document self-host safety defaults

Spell out why self-hosted instances never ship events upstream by default
(empty POSTHOG_API_KEY → noop client) and explain how operators can point
at their own PostHog project without any code change.

* feat(analytics): emit runtime_registered, issue_executed, team_invite_*

Three server-side funnel events, all gated on first-time state transitions
so retries and re-runs don't inflate the WAW buckets:

- runtime_registered fires from DaemonRegister when UpsertAgentRuntime
  reports (xmax = 0) — i.e. the row was inserted, not updated. Heartbeats
  and re-registrations stay silent.
- issue_executed fires from CompleteTask after an atomic
  UPDATE issue SET first_executed_at = now() WHERE id = $1 AND
  first_executed_at IS NULL flips the column for the first time. Retries,
  re-assignments, and comment-triggered follow-up tasks hit the WHERE
  clause and no-op. Carries nth_issue_for_workspace so the ≥1/≥2/≥5/≥10
  buckets filter without extra queries.
- team_invite_sent fires from CreateInvitation and team_invite_accepted
  from AcceptInvitation, closing the expansion funnel.

Adds a 050 migration for issue.first_executed_at plus a partial index so
the workspace-scoped executed-count query doesn't scan the never-executed
tail.

* feat(config): surface PostHog key via /api/config

Extends AppConfig with posthog_key / posthog_host sourced from env on
every request (so operators can rotate the key via secret refresh without
a restart). Reading the key off the server — rather than baking it into
the frontend bundle via NEXT_PUBLIC_* — means self-hosted instances
inherit the blank key automatically and never ship events upstream.

* feat(analytics): wire posthog-js identify + UTM capture on the client

Adds @multica/core/analytics — a thin wrapper around posthog-js that owns
attribution capture and identity merge. Posthog-js config comes from
/api/config (not NEXT_PUBLIC_*), so self-hosted instances whose server
returns an empty key automatically run the SDK inert.

captureSignupSource stamps a multica_signup_source cookie with UTM params
and the referrer's origin (never the full referrer — that can leak OAuth
code/state in the callback URL). The backend signup event reads this
cookie on new-user creation.

Identity flows:
- auth-initializer fires identify() right after getMe() resolves, on both
  cookie and token paths. A getConfig/getMe race is handled by buffering
  a pending identify inside the analytics module and flushing it once
  initAnalytics finishes.
- auth store calls identify() on verifyCode / loginWithGoogle /
  loginWithToken and resetAnalytics() on logout so the next login merges
  cleanly without bleeding events.

* docs(analytics): describe runtime_registered, issue_executed, invite events

Fills in the schema for the remaining funnel events. Captures the
design commentary that belongs next to the contract rather than in a PR
description — in particular why issue_executed uses the atomic
first_executed_at flip instead of counting task-terminal events, and why
runtime_registered relies on xmax = 0 rather than a query-then-write.

* fix(analytics): drop non-atomic nth_issue_for_workspace from issue_executed

Computing the workspace's Nth-issue ordinal at emit time is not atomic
under concurrent first-completions — two transactions can both run
MarkIssueFirstExecuted, then both run CountExecutedIssuesInWorkspace, and
both observe count=1 before either has committed, so both events go out
stamped as n=1. Serialising it would mean a per-workspace advisory lock
or a SERIALIZABLE-isolated tx; PostHog answers the same question exactly
at query time via row_number() partitioned by workspace_id, so the
emit-time property adds risk without adding information.

Removes the property from analytics.IssueExecuted, deletes the unused
CountExecutedIssuesInWorkspace query, and regenerates sqlc. The partial
index stays — any future workspace-scoped executed-issue query will want
it.

* fix(analytics): wire $pageview and harden signup_source cookie payload

Two frontend fixes from the PR review:

- PageviewTracker, mounted under WebProviders, fires capturePageview on
  every Next.js App Router path / query-string change. Without this the
  capturePageview helper in @multica/core/analytics was never called and
  the acquisition funnel's / → signup step was empty.
- captureSignupSource now caps each UTM / referrer value at 96 chars
  *before* JSON.stringify, and drops the whole cookie when the serialised
  payload still exceeds 512 chars. Previously the overall slice(0, 256)
  could leave a half-JSON string on the wire that neither the backend nor
  PostHog could parse.

Both capturePageview and identify now buffer a single pending call when
fired before initAnalytics resolves — otherwise the initial "/" pageview
and same-turn login identify race the /api/config fetch and get dropped.
resetAnalytics clears both buffers so a logout→login cycle stays clean.

* fix(analytics): URL-decode signup_source cookie on read

Go does not URL-decode Cookie.Value automatically, so the frontend's
JSON-then-encodeURIComponent payload was landing in PostHog as
percent-encoded garbage (%7B%22utm_source...). Unescape on read so the
backend receives the original JSON string the frontend intended, and
drop values that fail to decode or exceed the server-side cap — sending
truncated garbage is worse than sending nothing. Oversized-cookie guard
matches the frontend's SIGNUP_SOURCE_MAX_LEN.

* docs(analytics): reflect nth-issue drop, $pageview wiring, cookie encoding

Pulls the schema doc back in line with the code: issue_executed no longer
advertises nth_issue_for_workspace (with a note about why PostHog derives
it at query time instead), the frontend $pageview section names the
actual PageviewTracker component that fires it, and the signup_source
section documents the per-value cap / overall drop rule and the
encode-on-write / decode-on-read contract.

---------

Co-authored-by: Jiang Bohan <bhjiang@outlook.com>
2026-04-21 14:42:52 +08:00
Bohan Jiang
824d943848 fix(auth): derive cookie Secure flag from FRONTEND_ORIGIN scheme (#1390)
The session cookie's Secure flag was tied to APP_ENV, and the
docker-compose self-host stack defaults APP_ENV to "production". On
plain-HTTP self-host deployments (LAN IP, private network) the browser
silently drops Secure cookies, leaving every subsequent /api/* call
anonymous and surfacing as 401 "auth: no token found" right after a
successful login.

Derive Secure from the scheme of FRONTEND_ORIGIN so HTTPS origins get
Secure cookies and plain-HTTP origins get non-secure cookies the
browser will actually store. Also harden cookieDomain() against the
other common trap: COOKIE_DOMAIN=<ip>, which RFC 6265 forbids and
browsers reject. Log a one-shot warning and fall back to host-only.

Docs: correct the COOKIE_DOMAIN description (it was labelled as
CloudFront-only but applies to session cookies too) and call out the
IP-literal pitfall in SELF_HOSTING_ADVANCED.md, self-hosting.mdx, and
.env.example.

Refs #1321
2026-04-20 19:53:15 +08:00
LinYushen
07034f4455 feat(server): configurable pgxpool size with sane defaults (#1381)
* feat(server): configurable pgxpool size with sane defaults

pgxpool.New(ctx, url) silently sets MaxConns = max(4, NumCPU). On the
prod pods that resolved to 4, which got fully saturated by daemon
claim/heartbeat traffic (~3800 acquires/s) and showed up as ~900ms
acquire waits on every query — the actual root cause of the 3s+
/tasks/claim tail latency. The db pool stats logging from #1378
confirmed this with empty_acquire_delta == acquire_count_delta.

Switch to pgxpool.ParseConfig + NewWithConfig and apply per-pod
defaults of MaxConns=25 / MinConns=5, both overridable via env vars
(DATABASE_MAX_CONNS / DATABASE_MIN_CONNS) so the size can be tuned
in prod without a redeploy.

The defaults follow the standard 'small pool, lots of waiters' guidance
for Postgres (PG community / HikariCP formula
`(core_count * 2) + effective_spindle_count`); 25 leaves headroom for
bursts and occasional long queries while staying safely under typical
managed-Postgres max_connections ceilings when multiplied across pods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(server): respect DATABASE_URL pool_* params; add precedence tests

Address review feedback on #1381:

- Configuration precedence is now explicit: DATABASE_MAX_CONNS env >
  pool_max_conns query param on DATABASE_URL > built-in default. Same
  for min_conns. Previously the env-empty path unconditionally
  overwrote whatever ParseConfig had read from the URL — a silent
  regression for deployments that already tuned pool size via the
  connection string.
- Add unit tests in dbstats_test.go covering each precedence branch
  (defaults, URL-only, env-over-URL, partial URL, invalid env,
  min>max clamp).
- Move pool tuning vars out of 'Required Variables' into a new
  'Database Pool Tuning (Optional)' section in SELF_HOSTING_ADVANCED.md
  so self-hosters don't think they need to set them.
- Add commented entries in .env.example.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(server): invalid pool env falls back to URL/code default, never pgx 4

Address second round of review on #1381:

Previous code passed cfg.MaxConns / cfg.MinConns as the envInt32 fallback,
which meant an invalid DATABASE_MAX_CONNS value silently fell back to
ParseConfig's value — i.e. pgx's built-in default of 4/0 when the URL had
no pool_* params. That's exactly the bad value this PR exists to remove,
and the previous test (TestPoolSizing_InvalidEnvFallsBack) accidentally
locked it in.

Compute the non-env fallback first (URL pool_* if present, else code
default 25/5) and pass that to envInt32. Misconfigured env now lands on
the same value as if the env were unset — never on the pgx default.

Replace the loose 'max > 0' assertion with two precise tests:
- invalid env + no URL param → code default (25/5)
- invalid env + URL param    → URL value

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 17:07:19 +08:00
Bohan Jiang
96ee5bba52 docs(selfhost): surface APP_ENV + 888888 gating in .env.example (#1361)
The v0.2.6 self-host security fix (#1307) defaults APP_ENV to "production"
in docker-compose.selfhost.yml, which disables the 888888 master verification
code. The follow-up docs PR (#1313) covered SELF_HOSTING.md and the
installers, but `.env.example` — the file users actually copy and edit —
still makes no mention of APP_ENV, so operators who don't read the prose
docs hit the exact same "888888 stopped working after upgrade" confusion
reported in #1331.

- Add APP_ENV= to .env.example with a comment block that spells out the three
  cases (Docker default, local dev, evaluation) and warns against enabling
  the bypass on public instances. Keeping the value empty preserves the
  current `make dev` UX (Go server reads empty → treats as non-production →
  888888 works locally) while `${APP_ENV:-production}` in the compose file
  still ensures public Docker deployments are safe by default.
- Update the existing 888888 mention under # Email so it no longer
  contradicts the new gating rule.
- Update the `make selfhost` post-start banner, which still told operators
  to "Log in with any email + verification code: 888888" even after #1307
  disabled that path by default.
2026-04-20 13:26:42 +08:00
Azaan Ali Raza
b428f36ca6 feat: add ALLOW_SIGNUP + ALLOWED_EMAIL_* for self-hosted instances (#1098)
Closes #930

- Added environment variables to control signups
- Updated frontend to hide signup text when disabled
- Added backend check to block new user creation via magic link
- Updated .env.example
2026-04-19 21:02:42 -07:00
LinYushen
5dad1f0915 fix(selfhost): clear hardcoded NEXT_PUBLIC_API_URL/WS_URL defaults (#1063)
The .env.example had hardcoded http://localhost:8080 defaults for
NEXT_PUBLIC_API_URL and NEXT_PUBLIC_WS_URL. When users copied .env.example
to .env and customized the backend port, the old defaults would still get
baked into the frontend at docker build time via NEXT_PUBLIC_WS_URL build
arg, causing API/WebSocket connection failures.

With empty defaults:
- Docker selfhost: frontend uses relative paths, Next.js rewrites proxy
  to backend internally — works regardless of external port config
- Local dev (make dev): Makefile sets these to localhost:$PORT automatically
- Browser fallback: deriveWsUrl() auto-derives WebSocket URL from page
  origin when NEXT_PUBLIC_WS_URL is empty

Closes #1055

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:56:30 +08:00
LinYushen
c0db3e0e76 Revert "feat(selfhost): add single-domain Caddy setup (#899)" (#1062)
This reverts commit 100146c49e.
2026-04-15 14:44:47 +08:00
KimSeongJun
100146c49e feat(selfhost): add single-domain Caddy setup (#899)
* selfhost: add single-domain caddy setup

* fix(selfhost): address Caddy review feedback
2026-04-14 20:20:26 -07:00
LinYushen
95bfd7dd96 feat(auth): migrate auth token to HttpOnly Cookie & WebSocket Origin whitelist (#819)
* feat(auth): migrate auth token to HttpOnly cookie & implement WebSocket Origin whitelist

Security improvements from the MUL-566 audit report:

1. Auth token is now set as an HttpOnly, SameSite=Lax cookie on login,
   preventing XSS-based token theft. Cookie-based auth includes CSRF
   protection via double-submit cookie pattern. The Authorization header
   path is preserved for Electron desktop app and CLI/PAT clients.

2. WebSocket upgrader now validates the Origin header against a
   configurable allowlist (ALLOWED_ORIGINS env var), rejecting
   connections from unauthorized origins.

Backend: new auth cookie helpers, middleware reads cookie as fallback,
WS handler accepts cookie auth, Origin whitelist, logout endpoint.
Frontend: CSRF token in API headers, cookie-aware auth store and WS
client, web app opts into cookieAuth mode while desktop keeps tokens.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(auth): address PR review — Strict cookies, HMAC-bound CSRF, origin sync

1. SameSite=Lax → SameSite=Strict per spec requirement
2. CSRF token now HMAC-signed with auth token (nonce.signature format),
   preventing subdomain cookie injection attacks
3. allowedWSOrigins uses atomic.Value to eliminate data race
4. Removed magic "cookie" sentinel string in WSProvider — pass null token
   and guard with boolean check instead
5. Removed dead delete uploadHeaders["Content-Type"] in API client

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 12:13:35 +08:00
Antar Das
d32c419b6d feat(storage): add local file storage fallback (#710)
* feat(storage): add local file storage fallback

- Add local storage implementation for file uploads
- Update .env.example with LOCAL_UPLOAD_DIR and LOCAL_UPLOAD_BASE_URL
- Integrate local storage into server router and handlers
- Add storage abstraction layer with util functions

* ♻️ refactor(storage): improve path handling and file serving

switch from path to filepath for better cross-platform support and replace manual file serving logic with http.ServeFile to enhance security against path traversal. update unit tests to use t.Setenv for cleaner environment variable management.
2026-04-12 14:04:22 +08:00
Jiayuan Zhang
f100b5b707 fix(auth): graceful email degradation for self-hosting (#742)
* fix(auth): log email send errors and gracefully degrade in non-production

In non-production environments (APP_ENV != "production"), if sending the
verification code email fails, log the error as a warning and still return
success. This lets self-hosting users log in with the master code (888888)
even when their Resend configuration is incomplete (e.g. unverified from-domain).

In production, the behavior is unchanged — email failures return 500.

Also adds guidance in .env.example about RESEND_FROM_EMAIL for self-hosters.

Closes #723

* fix(auth): remove APP_ENV degradation, keep error logging only

Remove the APP_ENV-based graceful degradation for email send failures
— it's risky if users forget to set APP_ENV=production. Instead, always
return 500 on email failure (safe for production) and rely on the error
log (slog.Error) with the actual Resend error for debugging.

Self-hosters who don't need real emails should leave RESEND_API_KEY empty
(codes print to stdout, master code 888888 works).
2026-04-12 02:30:01 +08:00
Jiang Bohan
14fe8e9df9 feat(auth): add Google OAuth login
Support Google login that links to existing accounts by email.
When a user who registered via email OTP signs in with Google using
the same email, they are linked to the same account.

Backend:
- Add POST /auth/google endpoint that exchanges Google auth code for
  tokens, fetches user profile, and calls findOrCreateUser()
- Updates user name and avatar from Google profile on first Google login

Frontend:
- Add "Continue with Google" button on login page (shown when
  NEXT_PUBLIC_GOOGLE_CLIENT_ID is configured)
- Add /auth/callback page to handle Google OAuth redirect
- Add loginWithGoogle to auth store and API client
2026-04-07 15:25:26 +08:00
Jiang Bohan
89bedb8f5c feat(web): support REMOTE_API_URL env for proxying to remote backend
- Load root .env in next.config.ts so REMOTE_API_URL is available
- Default fallback remains localhost:8080 (no impact on existing setups)
- Add REMOTE_API_URL to .env.example with documentation
2026-03-31 16:53:32 +08:00
yushen
29a80e057e feat(upload): add file upload API with S3 + CloudFront signed cookies
Add POST /api/upload-file endpoint that uploads files to S3 and returns
CDN URLs protected by CloudFront signed cookies (same pattern as Linear).

Infrastructure:
- Two private S3 buckets (static.multica.ai, static-staging.multica.ai)
- Two CloudFront distributions with OAC and Trusted Key Groups
- ACM wildcard cert in us-east-1, DNS records in Route 53
- RSA signing key stored in AWS Secrets Manager

Backend:
- S3 storage service with CloudFront CDN domain support
- CloudFront signed cookie generation (RSA-SHA1)
- Private key loaded from Secrets Manager (env var fallback for local dev)
- Cookies set on login (VerifyCode) with 72h expiry matching JWT
- Upload handler: multipart form → S3 → CloudFront URL response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 14:41:17 +08:00
LinYushen
5c9c2f69fd feat(auth): email verification login and personal access tokens
* feat(auth): add email verification login flow with 401 auto-redirect

Replace the old OAuth-based login with email verification codes:
- Backend: send-code / verify-code endpoints, verification_codes table (migration 009), rate limiting, Resend email service
- Frontend: two-step login UI (email → 6-digit OTP), auth store with sendCode/verifyCode
- SDK: ApiClient gains onUnauthorized callback; 401 responses auto-clear token and redirect to /login
- Fix login button staying disabled due to global isLoading state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(auth): add brute-force protection, redirect loop guard, and expired code cleanup

- VerifyCode: increment attempts on wrong code, reject after 5 failed tries (migration 010)
- onUnauthorized: skip redirect if already on /login to prevent infinite loops
- SendCode: best-effort cleanup of expired verification codes older than 1 hour

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(auth): add master verification code for non-production environments

Allow code "888888" to bypass email verification in non-production
environments to simplify development and testing workflows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(auth): add personal access tokens for CLI and API authentication

Add full-stack PAT support: users create tokens in Settings, CLI authenticates
via `multica auth login`. Server stores SHA-256 hashes only. Auth middleware
extended to accept both JWTs and PATs (distinguished by `mul_` prefix).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:32:30 +08:00
Jiayuan Zhang
2c28c4cba2 refactor(dev): share postgres across main and worktrees 2026-03-24 14:27:35 +08:00
Jiayuan Zhang
cdfa63af15 feat(runtime): add local codex daemon pairing 2026-03-24 12:03:14 +08:00
Jiayuan Zhang
81e64e9fce Add workspace management and isolated worktree environments 2026-03-23 18:12:11 +08:00
Jiayuan Zhang
d4f5c5b16f feat: pivot to AI-native task management platform (#232)
Replace the agent framework codebase with a new monorepo structure
for an AI-native Linear-like product where agents are first-class citizens.

New architecture:
- server/ — Go backend (Chi + gorilla/websocket + sqlc)
  - API server with REST routes for issues, agents, inbox, workspaces
  - WebSocket hub for real-time updates
  - Local daemon entry point for agent runtime connection
  - PostgreSQL migration with 13 tables (issue, agent, inbox, etc.)
  - WebSocket protocol types for server<->daemon communication
- apps/web/ — Next.js 16 frontend
  - Dashboard layout with sidebar navigation
  - Route skeleton: inbox, issues, agents, board, settings
- packages/ui/ — Preserved shadcn/ui design system (26+ components)
- packages/types/ — Full API contract types (Issue, Agent, Workspace, Inbox, Events)
- packages/sdk/ — REST ApiClient + WebSocket WSClient
- packages/store/ — Zustand stores (issue, agent, inbox, auth)
- packages/hooks/ — React hooks (useIssues, useAgents, useInbox, useRealtime)
- packages/utils/ — Shared utilities

Removed: apps/cli, apps/desktop, apps/mobile, apps/gateway,
packages/core, skills/, and all agent-framework code.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 17:55:49 +08:00
Jiayuan Zhang
005908710e chore: add local dev script for Gateway + Desktop with Telegram bot
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 00:39:43 +08:00
Jiayuan
c54e768016 docs: update credential setup 2026-02-01 02:28:43 +08:00
yushen
a9dcde124b docs: add .env.example and environment configuration guide
- Add .env.example with all supported provider env vars
  (OpenAI, Anthropic, DeepSeek, Kimi, Groq, Mistral, Together, Google)
- Update README with environment setup instructions
- Document configuration priority and startup commands
- Whitelist .env.example in .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 13:32:01 +08:00