Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8963eed
research: native Zotero & Google Docs integration with approval gate …
Jetezh Oct 10, 2026
48cc90e
research: add Google OAuth setup guide modal and clean up internal to…
Jetezh Oct 10, 2026
ad8e377
research: persist active doc url, conditional gdocs inputs, and deskt…
Jetezh Oct 10, 2026
65e315a
research: enforce gdocs_update over browser tool, auto-reload and ref…
Jetezh Oct 10, 2026
80d285f
research: full Google OAuth 2.0 flow, REST API batchUpdate, and safe …
Jetezh Oct 10, 2026
3081bcf
security: strict input validation & comprehensive test suite for anti…
Jetezh Oct 11, 2026
ffd0a47
research: resizable split view width and Google Docs live edit mode t…
Jetezh Oct 11, 2026
170c6fd
research: switch toggle for edit mode and full google docs toolbar in…
Jetezh Oct 11, 2026
268e0b2
research: Zotero Named Range citation, journal formatting, and live L…
Jetezh Oct 11, 2026
c1697a3
oauth: dynamic redirect URI, Google OAuth state persistence, desktop …
Jetezh Oct 11, 2026
3714c5b
gdocs: add find_text anchor search and targeted chapter replacement
Jetezh Oct 11, 2026
1f32eec
gdocs: parse tables & headings, add table creation, before/after diff…
Jetezh Oct 11, 2026
148d183
research: add document draft tab, single-click Google Drive export, a…
Jetezh Oct 16, 2026
a7e77ae
research: live interactive draft tools (draft_read/draft_update) and …
Jetezh Oct 16, 2026
2207af5
fix: allow emptying Google Docs URL and support inline SVG rendering …
Jetezh Oct 16, 2026
6910be9
research: frameless academic diagram rendering and clean light-mode p…
Jetezh Oct 16, 2026
a489b6c
research: Google Docs solid bordered table style, word search bar (Ct…
Jetezh Oct 16, 2026
acd1fa0
fix(markdown): strict HTML tag block parsing, robust table border ren…
Jetezh Oct 16, 2026
1bf5392
fix(gdocs): client-side SVG-to-PNG conversion for Google Drive export…
Jetezh Oct 16, 2026
ac08fca
format: default academic document typography (Times New Roman 12pt, l…
Jetezh Oct 16, 2026
88e445e
fix(gdocs): clean unclosed HTML paragraph tags, convert HTML tables t…
Jetezh Oct 16, 2026
f5d26ee
format: proportional table column widths by header/content and visual…
Jetezh Oct 16, 2026
6fe6e31
format: strict academic hierarchy (Bab 12pt bold, sub-bab 12pt bold, …
Jetezh Oct 17, 2026
718aceb
fix(gdocs): preserve bold text weight during Google Docs API enforcem…
Jetezh Oct 17, 2026
00ccc00
format: support hierarchical nested lists (parent-to-child) in Markdo…
Jetezh Oct 17, 2026
f019141
fix(chat): restore normal modern chat bubble styling and isolate acad…
Jetezh Oct 17, 2026
23fdfea
fix(gdocs): continuous ordered list numbering (1,2,3...) in single co…
Jetezh Oct 17, 2026
6a19143
fix(gdocs): sanitize SVG XML entities (&) to prevent Canvas parse…
Jetezh Oct 17, 2026
3f3ec75
perf: isolate ResearchPanel & ProjectSidebar with memo, stabilize cal…
Jetezh Oct 18, 2026
dc9645b
feat(gdocs): add auto-share writer permission on export and interacti…
Jetezh Oct 18, 2026
5af7e30
feat(gdocs): two-way sync (Push updates to Google Docs & Pull latest …
Jetezh Oct 18, 2026
2800c81
style(research): compact icon-only toolbar with descriptive tooltips …
Jetezh Oct 18, 2026
8c61a09
i18n: complete multi-language tooltip translations across research pa…
Jetezh Oct 18, 2026
d95a52a
fix(chat): completely isolate academic draft rendering into dedicated…
Jetezh Oct 18, 2026
8314733
fix(chat): code-split ResearchPanel with dynamic import so KaTeX and …
Jetezh Oct 18, 2026
92c9ef1
feat(research): academic draft styling, KaTeX isolation, Google Docs …
Jetezh Oct 18, 2026
ad25db2
docs: add screenshots for academic draft, Google Docs sync & LaTeX ed…
Jetezh Oct 19, 2026
adc476d
feat(research): session-scoped academic drafts, gdocs sync isolation …
Jetezh Oct 21, 2026
972ff69
chore: bump version to 0.9.0 for chat folder tree and file viewer fea…
Jetezh Oct 21, 2026
9237972
feat(chat): hierarchical list nesting support, collapsible folder tre…
Jetezh Oct 21, 2026
e719530
feat(research): zero-config Word (.docx) export for manual Google Doc…
Jetezh Oct 22, 2026
0cee074
fix(chat): support pasting screenshots directly from Linux clipboard …
Jetezh Oct 22, 2026
16c7195
feat(chat): embedded code editor and PTY terminal workspace
Jetezh Oct 22, 2026
ce1f129
fix(terminal): allow WebSocket upgrade through logging middleware
Jetezh Oct 22, 2026
41759ba
feat(chat): standalone terminal tab and file tree management
Jetezh Oct 23, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ AGENTS.md

# Release artifacts
dist/
stage/
!internal/server/dist/
!internal/server/dist/.gitkeep
web/tsconfig.tsbuildinfo
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
SHELL := /bin/bash

-include .env
export

# Resolve each tool from $PATH first, then fall back to its common install
# location. Override any of them explicitly, e.g. `make GO=/opt/go/bin/go build`.
# `tool <name> <fallback>` = the binary on PATH if present, else the fallback.
Expand Down Expand Up @@ -30,7 +33,7 @@ help: ## Show this help

.PHONY: dev
dev: ## Run backend (Air hot reload) and frontend (Vite HMR) together
@echo "Antares dev: API :8787 · Web :5173"
@echo "Antares dev: API :8790 · Web :5174"
@trap 'kill 0' EXIT INT TERM; \
$(MAKE) dev-api & \
$(MAKE) dev-web & \
Expand All @@ -42,7 +45,7 @@ dev-api: ## Backend with hot reload

.PHONY: dev-web
dev-web: ## Frontend dev server with HMR
@cd web && $(BUN) x vite
@cd web && $(BUN) x vite --port 5174

.PHONY: tui
tui: ## Hot-reload the TUI preview in the foreground (real terminal)
Expand Down
4 changes: 4 additions & 0 deletions cmd/antares/device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ func TestDevicePairCLI(t *testing.T) {
t.Setenv("ANTARES_HOME", home)
t.Setenv("ANTARES_CONFIG", "")
t.Setenv("ANTARES_PORT", "18999")
if _, err := config.Reload(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _, _ = config.Reload() })

var out bytes.Buffer
if err := cmdDevicePair(&out, []string{"--name", "Test Mac", "--platform=desktop-macos", "--json"}); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion desktop/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

// Version is the desktop shell's version (set with -ldflags -X main.Version=…).
var Version = "0.1.0"
var Version = "0.11.0"

//go:embed all:frontend
var frontendFS embed.FS
Expand Down
43 changes: 41 additions & 2 deletions desktop/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"net/url"
"os/exec"
"runtime"
"strings"
"sync"
Expand Down Expand Up @@ -94,7 +95,7 @@ func (s *Shell) onPageMessage(msg string, origin *application.OriginInfo) {
s.app.Logger.Debug("page message", "msg", msg, "origin", origin.Origin)
from := originOf(origin.Origin)
bundled := strings.HasPrefix(from, "wails://")
dashboard := from != "" && from == s.mgr.CurrentOrigin()
dashboard := from != "" && isSameDashboardOrigin(from, s.mgr.CurrentOrigin())
if !bundled && !dashboard {
return
}
Expand All @@ -107,13 +108,36 @@ func (s *Shell) onPageMessage(msg string, origin *application.OriginInfo) {
}
switch u.Scheme {
case "http", "https", "mailto":
_ = s.app.Browser.OpenURL(u.String())
if err := s.app.Browser.OpenURL(u.String()); err != nil {
s.app.Logger.Warn("app.Browser.OpenURL failed, trying system open", "err", err)
openWithSystemDefault(u.String())
} else {
// Fallback on Linux if WebKit/Wails browser fails silently
openWithSystemDefault(u.String())
}
}
case strings.HasPrefix(msg, "antares:path:") && dashboard:
s.mgr.PagePath(origin.Origin, strings.TrimPrefix(msg, "antares:path:"))
}
}

func isSameDashboardOrigin(a, b string) bool {
if a == b {
return true
}
ua, errA := url.Parse(a)
ub, errB := url.Parse(b)
if errA != nil || errB != nil {
return false
}
if ua.Port() != ub.Port() {
return false
}
isLoopbackA := ua.Hostname() == "127.0.0.1" || ua.Hostname() == "localhost"
isLoopbackB := ub.Hostname() == "127.0.0.1" || ub.Hostname() == "localhost"
return isLoopbackA && isLoopbackB
}

func originOf(raw string) string {
u, err := url.Parse(raw)
if err != nil || u.Scheme == "" || u.Host == "" {
Expand All @@ -122,6 +146,21 @@ func originOf(raw string) string {
return strings.ToLower(u.Scheme + "://" + u.Host)
}

func openWithSystemDefault(targetURL string) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "linux":
cmd = exec.Command("xdg-open", targetURL)
case "darwin":
cmd = exec.Command("open", targetURL)
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", targetURL)
}
if cmd != nil {
_ = cmd.Start()
}
}

// ---- window, menu, tray ----

func (s *Shell) showWindow() {
Expand Down
12 changes: 11 additions & 1 deletion dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ set -e
ROOT="$(cd "$(dirname "$0")" && pwd)"
cd "$ROOT"

if [ -f "$ROOT/.env" ]; then
set -a
source "$ROOT/.env"
set +a
fi

if [ -n "$ANTARES_HOME" ]; then
mkdir -p "$ANTARES_HOME"
fi

# Colors
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
Expand Down Expand Up @@ -33,7 +43,7 @@ if [ ! -x "$BUN" ]; then
fi

echo -e "${CYAN}Antares Dev${NC}"
echo -e " Backend: ${GREEN}http://localhost:8787${NC}"
echo -e " Backend: ${GREEN}http://localhost:${ANTARES_PORT:-8790}${NC}"
echo -e " Frontend: ${GREEN}http://localhost:${VITE_PORT:-5174}${NC}"
echo -e " ${YELLOW}Open http://localhost:${VITE_PORT:-5174} in your browser${NC}"
echo ""
Expand Down
Binary file added docs/screenshots/01-academic-draft-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/02-gdocs-oauth-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/03-latex-editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,25 @@ go 1.26.3
replace github.com/coder/hnsw => github.com/tradik/hnsw v0.6.2-0.20260824091751-ad04fee59f41

require (
github.com/alecthomas/chroma/v2 v2.20.0
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/glamour v1.0.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/charmbracelet/x/ansi v0.11.6
github.com/chromedp/chromedp v0.16.0
github.com/coder/hnsw v0.6.2-0.20260622133054-36cab6028fed
github.com/coder/websocket v1.8.15
github.com/creack/pty v1.1.24
github.com/emersion/go-imap v1.2.1
github.com/enowdev/cloak-go v0.2.2-0.20260802105213-db0721c9e2e8
github.com/jackc/pgx/v5 v5.10.0
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728
github.com/muesli/termenv v0.16.0
github.com/pkg/sftp v1.13.9
github.com/sardanioss/httpcloak v1.6.8
github.com/yuin/goldmark v1.7.13
github.com/zalando/go-keyring v0.2.8
golang.org/x/crypto v0.54.0
golang.org/x/net v0.56.0
golang.org/x/term v0.45.0
Expand All @@ -28,7 +33,6 @@ require (
)

require (
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
Expand Down Expand Up @@ -84,9 +88,7 @@ require (
github.com/viterin/partial v1.1.0 // indirect
github.com/viterin/vek v0.4.2 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
github.com/zalando/go-keyring v0.2.8 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/sync v0.22.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -166,6 +170,8 @@ github.com/sardanioss/utls v1.10.3 h1:b1HU1+owWCUhfLUk0mk2mOgepL14rmthbA1PS8XGQX
github.com/sardanioss/utls v1.10.3/go.mod h1:3sXK05Ir31HiMGINYV1uMDFRv/z9JL+QY9ITi/WEbV0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand Down
42 changes: 42 additions & 0 deletions internal/agent/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,51 @@ You are running as a worker for another agent. Nobody is watching your stream.
"Keep replies reasonably short for a chat; do not emit raw HTML.\n")
}

if cfg.Research.Zotero.APIKey != "" || cfg.Research.GDocs.WebhookURL != "" || cfg.Research.GDocs.DocumentURL != "" {
b.WriteString("\n## Connected Academic & Document Integrations\n\n")
if cfg.Research.Zotero.APIKey != "" {
fmt.Fprintf(&b, "- Zotero Library: Connected (User ID: %s). You have access to the user's Zotero collections and can format citations using zotero_collections, zotero_search, and zotero_cite.\n", cfg.Research.Zotero.UserID)
}
if cfg.Research.GDocs.WebhookURL != "" || cfg.Research.GDocs.DocumentURL != "" {
targetDoc := cfg.Research.GDocs.DocumentURL
if targetDoc == "" {
targetDoc = cfg.Research.GDocs.WebhookURL
}
fmt.Fprintf(&b, "- Google Docs: Connected (Target document/bridge: %s).\n", targetDoc)
b.WriteString(" * CRITICAL: ALWAYS use `gdocs_read` to inspect document contents and `gdocs_update` to write text, headings, or citations.\n")
b.WriteString(" * CRITICAL TEMPLATE DISCIPLINE: DO NOT overwrite or wipe entire chapters. When a template already provides chapter/subchapter headings (e.g. 3.1, 3.2) or template placeholder guides, ALWAYS preserve the headings and structure! Read the template using `gdocs_read` (which displays both headings and template tables in Markdown format), then fill each section in-place using `find_text` with `position: 'after'` or targeted placeholder replacement.\n")
b.WriteString(" * TABLES: When the template includes a table (indicated by `<!-- [Tabel N ...] -->` in `gdocs_read`), examine the required columns and format, and use `gdocs_update` with `table: {rows, columns, data}` to create or populate matching formal tables.\n")
b.WriteString(" * UNDO / ROLLBACK: If a mistake is made during editing, immediately use `gdocs_rollback` to restore the previous content from the automatic snapshot.\n")
b.WriteString(" * When inserting citations with `gdocs_update`, pass `zotero_items` (or `item_keys`) so Antares embeds official Zotero Named Ranges (compatible with the Zotero Google Docs extension and automated bibliography generation).\n")
b.WriteString(" * NEVER use the `browser` tool for Google Docs. Google Docs renders inside an inaccessible Canvas/WebGL surface where browser DOM manipulation and typing will fail.\n")
}
b.WriteString("\n## Academic Writing & Journal Standards (LaTeX & Google Docs)\n\n")
b.WriteString("- Document Drafting & Two-Way Sync (Draft-First Pattern):\n")
b.WriteString(" * Whenever asked to write, draft, or revise a chapter (e.g. Bab 3 Bilik), report, or academic essay, ALWAYS use the `draft_update` tool! This tool directly writes to the live 'Draf Naskah' tab in the user's Split View.\n")
b.WriteString(" * Format the draft in clean, structured Markdown with headings (`#`, `##`), academic tables (`| Col 1 | Col 2 |`), and inline SVG flowcharts/diagrams.\n")
b.WriteString(" * The user can inspect the live rendered draft in their Split View, make manual adjustments, and click 'Ekspor ke Google Docs' to generate a native Google Doc on Google Drive with one click!\n")
b.WriteString(" * Two-way sync: Users can Push updates from Draft to Google Docs or Pull latest edits made by team members directly from Google Docs into the local Draft.\n")
b.WriteString(" * Use `draft_read` to inspect the current draft content before making modular revisions.\n")
b.WriteString("- Standard Typography: Use formal academic style (Times New Roman, 12pt, Justified alignment).\n")
b.WriteString("- Figures & Flowcharts:\n")
b.WriteString(" * For SVG diagrams in drafts, use clean academic styling: transparent or clean white background (`background: transparent`), clean dark/slate lines, legible fonts, and NO dark sci-fi cards or heavy artificial bezels so the diagram blends seamlessly into white paper and Google Docs.\n")
b.WriteString(" * Always place an explicit figure placeholder indicating what illustration or diagram to insert and where.\n")
b.WriteString(" * Always place the caption/legend BELOW the figure (e.g. `Gambar 2.1: Flowchart Metodologi Penelitian` or `Figure 2.1: System Architecture`).\n")
b.WriteString(" * In LaTeX, use standard figure environments: `\\begin{figure}[htbp] \\centering ... \\caption{...} \\label{fig:...} \\end{figure}`.\n")
b.WriteString("- Tables: Always place the caption/title ABOVE the table (e.g. `Tabel 2.1: Hasil Pengujian Akurasi` or `Table 2.1: Accuracy Metrics`).\n")
b.WriteString("- Mathematical Formulas & Equations:\n")
b.WriteString(" * In LaTeX, use native numbered equations (`\\begin{equation} ... \\end{equation}`) or inline math (`$...$`).\n")
b.WriteString("- Citations & Bibliography:\n")
b.WriteString(" * For LaTeX documents: Use `\\cite{key}` for in-text citations and maintain `references.bib` using BibTeX format (fetch BibTeX directly from Zotero using `zotero_cite` with `format=\"bibtex\"`).\n")
b.WriteString(" * For Google Docs: Use `gdocs_update` with `zotero_items` to insert official Zotero Named Ranges and formatted bibliography entries with hanging indent.\n")
}

if len(active) > 0 {
b.WriteString("\n## Tool notes\n\n")
b.WriteString("- Paths given to file tools are relative to the workspace; you cannot read outside it.\n")
if hasTool(active, "gdocs_update") || hasTool(active, "gdocs_read") {
b.WriteString("- Google Docs: When asked to read, write, edit, or update Google Docs, you MUST use gdocs_read and gdocs_update. Do NOT attempt to navigate to Google Docs using the browser tool.\n")
}
if hasTool(active, "read_file") || hasTool(active, "edit_file") {
// Harness guidance for the read → edit loop. The two tools share
// one contract — what a read returns is what an edit can find — so
Expand Down
24 changes: 24 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type Config struct {

Social Social `yaml:"social" json:"social"`

Research Research `yaml:"research" json:"research"`

// inline*FromEnv record that model.base_url / model.api_key were supplied by
// ANTARES_BASE_URL / ANTARES_API_KEY rather than read from config.yaml.
// ResolveProvider honours an env-supplied credential over a named provider's
Expand All @@ -74,6 +76,28 @@ type Social struct {
AutopilotEnabled bool `yaml:"autopilot_enabled" json:"autopilot_enabled"`
}

// Research configures academic & research integrations: Zotero and Google Docs.
type Research struct {
Zotero ZoteroConfig `yaml:"zotero" json:"zotero"`
GDocs GDocsConfig `yaml:"gdocs" json:"gdocs"`
}

type ZoteroConfig struct {
UserID string `yaml:"user_id" json:"user_id"`
APIKey string `yaml:"api_key" json:"api_key"`
}

type GDocsConfig struct {
Method string `yaml:"method" json:"method"` // "webhook" or "oauth"
WebhookURL string `yaml:"webhook_url" json:"webhook_url"`
DocumentURL string `yaml:"document_url" json:"document_url"`
DocumentID string `yaml:"document_id" json:"document_id"`
OAuthClientID string `yaml:"oauth_client_id" json:"oauth_client_id"`
OAuthClientSecret string `yaml:"oauth_client_secret" json:"oauth_client_secret"`
OAuthAccessToken string `yaml:"-" json:"-"`
OAuthRefreshToken string `yaml:"-" json:"-"`
}

// Model selects which LLM answers by default.
type Model struct {
Default string `yaml:"default" json:"default"`
Expand Down
7 changes: 7 additions & 0 deletions internal/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ func applyEnv(c *Config) {
str("TELEGRAM_BOT_TOKEN", &c.Gateway.Telegram.BotToken)
str("DISCORD_BOT_TOKEN", &c.Gateway.Discord.BotToken)

str("ZOTERO_USER_ID", &c.Research.Zotero.UserID)
str("ZOTERO_API_KEY", &c.Research.Zotero.APIKey)
str("GDOCS_METHOD", &c.Research.GDocs.Method)
str("GDOCS_WEBHOOK_URL", &c.Research.GDocs.WebhookURL)
str("GDOCS_OAUTH_CLIENT_ID", &c.Research.GDocs.OAuthClientID)
str("GDOCS_OAUTH_CLIENT_SECRET", &c.Research.GDocs.OAuthClientSecret)

// Provider API keys: explicit ANTARES_PROVIDER_<NAME>_API_KEY wins, then the
// provider's declared api_key_env, then conventional vendor variables.
for name, p := range c.Providers {
Expand Down
27 changes: 26 additions & 1 deletion internal/config/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ var common = map[string]bool{
"display.max_live_reasoning_chars": true,
"logging.level": true,
"server.host": true,

"research.zotero.user_id": true,
"research.zotero.api_key": true,
"research.gdocs.method": true,
"research.gdocs.webhook_url": true,
"research.gdocs.document_url": true,
"research.gdocs.oauth_client_id": true,
"research.gdocs.oauth_client_secret": true,
}

func tierFor(path string) string {
Expand All @@ -110,6 +118,10 @@ var hidden = map[string]bool{
// express "absent" versus "empty" for display.modules.
"display.modules": true,
"display.preset": true,

"research.gdocs.document_id": true,
"research.gdocs.oauth_access_token": true,
"research.gdocs.oauth_refresh_token": true,
}

var enums = map[string][]string{
Expand All @@ -122,9 +134,17 @@ var enums = map[string][]string{
"logging.level": {"debug", "info", "warn", "error"},
"tools.web_search.provider": {"browser", "brave", "tavily", "searxng", "none"},
"image_gen.reference_mode": {"edits", "generations"},
"research.gdocs.method": {"webhook", "oauth"},
}

var help = map[string]string{
"research.zotero.user_id": "Your personal Zotero User ID (from zotero.org/settings/keys).",
"research.zotero.api_key": "Your Zotero API Key with library and file permissions.",
"research.gdocs.method": "Google Docs connection method: webhook (Google Apps Script) or oauth (Google Cloud).",
"research.gdocs.webhook_url": "Apps Script Webhook URL for Google Docs updates.",
"research.gdocs.document_url": "Active Google Docs URL to read and edit.",
"research.gdocs.oauth_client_id": "Google Cloud OAuth Client ID for Google Docs API.",
"research.gdocs.oauth_client_secret": "Google Cloud OAuth Client Secret for Google Docs API.",
"model.default": "Model id as your provider spells it, e.g. anthropic/claude-sonnet-4.5.",
"model.provider": "Which entry under providers to call.",
"model.auxiliary": "Cheaper model used for summarising and other background work.",
Expand Down Expand Up @@ -392,9 +412,14 @@ func redactMap(m map[string]any, prefix string) {
}
}

func maskSecret(s string) string {
// MaskSecret returns a masked representation of a secret string.
func MaskSecret(s string) string {
if len(s) <= 8 {
return "••••••••"
}
return s[:4] + strings.Repeat("•", 8) + s[len(s)-4:]
}

func maskSecret(s string) string {
return MaskSecret(s)
}
Loading