Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ dist/
*.test
*.out

# Web frontend local artifacts
node_modules/
web/app/playwright-report/
web/app/test-results/
web/app/.vite/
web/static-dist/*
!web/static-dist/
!web/static-dist/.gitkeep

# Local binaries
/csgclaw

Expand All @@ -22,6 +31,9 @@ docker/
.cursor/
.idea/
.vscode/
!.vscode/
!.vscode/settings.json
!.vscode/extensions.json

# Vendored native libraries / SDK dist fetched on demand
third_party/boxlite-go/libboxlite.a
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.13.0
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
44 changes: 44 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"editor.formatOnSave": true,
"prettier.requireConfig": true,
"prettier.configPath": "web/app/.prettierrc.json",
"eslint.workingDirectories": [
{
"directory": "web/app",
"changeProcessCWD": true
}
],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
}
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ internal/config/ config defaults, load/save
internal/api/ HTTP handlers and router
internal/im/ IM service and PicoClaw bridge
internal/server/ HTTP server and UI wiring
web/static/ shipped frontend assets
web/app/ Web UI development source and Vite project
web/static/ legacy frontend assets retained for comparison
web/static-dist/ generated Web UI assets for Go embed; run make web-build
```

## Commands
Expand All @@ -42,6 +44,7 @@ make release
- Prefer existing patterns and the standard library before adding dependencies.
- Format with `make fmt`.
- Add or update tests when changing CLI, config, API, or runtime behavior.
- When changing the Vite web app, follow `docs/web/FRONTEND.md` for frontend structure, source layout, components, styling, state, accessibility, and verification.
- Do not change BoxLite sandbox integration or packaging paths unless the task is about sandbox/runtime integration.
- When changing config fields or defaults, update loader, saver, onboard flow, tests, and docs together.
- Never hardcode or print real secrets; startup and logs must keep tokens redacted.
Expand All @@ -57,5 +60,6 @@ make release

- `README.md`
- `docs/README.go.md`
- `docs/web/FRONTEND.md`
- `Makefile`
- `.github/workflows/release.yml`
80 changes: 72 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ BOXLITE_CLI_BASE_URL ?= https://github.com/boxlite-ai/boxlite/releases/download

GO ?= go
GOFMT ?= gofmt
WEB_APP_DIR ?= web/app
WEB_STATIC_DIST_DIR ?= web/static-dist
WEB_PNPM ?= $(CURDIR)/scripts/web-pnpm.sh
TARGET_OS ?= $(shell $(GO) env GOOS)
TARGET_ARCH ?= $(shell $(GO) env GOARCH)
CLI_BIN ?= $(BIN_DIR)/csgclaw-cli
Expand All @@ -25,16 +28,21 @@ LOCAL_IMAGE ?= picoclaw:local

.DEFAULT_GOAL := build-all

.PHONY: help fmt test build build-csgclaw build-csgclaw-cli build-csgclaw-cli-for-picoclaw build-all run clean package package-all release tag push publish
.PHONY: help fmt test check-web-toolchain check-web-layout ensure-web-deps web-install web-dev build-web build build-server build-csgclaw build-csgclaw-cli build-csgclaw-cli-for-picoclaw build-all run clean package package-all release tag push publish

help:
@printf '%s\n' \
'make fmt - format Go files' \
'make test - run Go tests' \
'make build - build $(BIN)' \
'make web-install - install Web UI dependencies' \
'make web-dev - run Vite Web UI dev server' \
'make build-web - build Web UI app into web/static-dist' \
'make build - build Web UI, then $(BIN)' \
'make build-server - build $(BIN) without rebuilding Web UI' \
'make build-csgclaw - alias for build-server' \
'make build-csgclaw-cli - build $(CLI_BIN) for TARGET_OS/TARGET_ARCH (defaults to current platform)' \
'make build-csgclaw-cli-for-picoclaw - build PicoClaw CLI binaries for linux/amd64 and linux/arm64' \
'make build-all - build bin/csgclaw and bin/csgclaw-cli' \
'make build-all - build Web UI, bin/csgclaw, and bin/csgclaw-cli' \
'make run - run the server in foreground' \
'make package - package APP binary into dist/' \
'make package-all - package csgclaw and csgclaw-cli for current platform' \
Expand All @@ -45,17 +53,71 @@ help:
'make publish - tag and push manager image'

fmt:
$(GOFMT) -w $(shell find cli cmd internal -name '*.go')
$(GOFMT) -w $(shell find cli cmd internal web -name '*.go')

test:
env GOCACHE=$(GOCACHE) $(GO) test ./...

build:
check-web-toolchain:
$(WEB_PNPM) --check

check-web-layout:
@if [ ! -d "$(WEB_APP_DIR)" ]; then \
printf '%s\n' "Web UI source directory is missing: $(WEB_APP_DIR)."; \
printf '%s\n' "Run make from the csgclaw repository root, or set WEB_APP_DIR=/absolute/path/to/web/app."; \
exit 1; \
fi
@if [ ! -f "$(WEB_APP_DIR)/package.json" ]; then \
printf '%s\n' "Web UI package.json is missing: $(WEB_APP_DIR)/package.json."; \
exit 1; \
fi
@if [ ! -f "$(WEB_APP_DIR)/pnpm-lock.yaml" ]; then \
printf '%s\n' "Web UI pnpm lockfile is missing: $(WEB_APP_DIR)/pnpm-lock.yaml."; \
printf '%s\n' "Restore the lockfile before running make build-web."; \
exit 1; \
fi

ensure-web-deps: check-web-toolchain check-web-layout
@if [ ! -d "$(WEB_APP_DIR)/node_modules" ] || [ ! -x "$(WEB_APP_DIR)/node_modules/.bin/vite" ]; then \
printf '%s\n' "Web UI dependencies are missing; running make web-install before build."; \
$(MAKE) web-install; \
fi

web-install: check-web-toolchain check-web-layout
@printf '%s\n' "Installing Web UI dependencies in $(WEB_APP_DIR)."
@printf '%s\n' "If this appears stuck on registry downloads, check npm registry network/proxy access."
@$(WEB_PNPM) install --frozen-lockfile || { \
status=$$?; \
printf '%s\n' "Failed to install Web UI dependencies."; \
printf '%s\n' "Check npm registry network/proxy access, then rerun make web-install or make build-web."; \
exit $$status; \
}

web-dev: ensure-web-deps
$(WEB_PNPM) dev

build-web: ensure-web-deps
@mkdir -p "$(WEB_STATIC_DIST_DIR)"
@$(WEB_PNPM) build || { \
status=$$?; \
printf '%s\n' "Failed to build Web UI."; \
printf '%s\n' "If the error mentions vite not found, rerun make web-install and check the install output."; \
exit $$status; \
}
@test -f "$(WEB_STATIC_DIST_DIR)/index.html" || { \
printf '%s\n' "Web UI build did not produce $(WEB_STATIC_DIST_DIR)/index.html."; \
exit 1; \
}

build: build-web
$(MAKE) build-server

build-server:
mkdir -p $(BIN_DIR)
env GOCACHE=$(GOCACHE) $(GO) build -ldflags "$(LDFLAGS)" -o $(BIN) $(CMD_PATH)

build-csgclaw:
$(MAKE) build APP=csgclaw
$(MAKE) build-server APP=csgclaw

build-csgclaw-cli:
mkdir -p $(BIN_DIR)
Expand All @@ -65,9 +127,11 @@ build-csgclaw-cli-for-picoclaw:
$(MAKE) build-csgclaw-cli TARGET_OS=linux TARGET_ARCH=amd64 CLI_BIN=$(BIN_DIR)/csgclaw-cli_linux_amd64
$(MAKE) build-csgclaw-cli TARGET_OS=linux TARGET_ARCH=arm64 CLI_BIN=$(BIN_DIR)/csgclaw-cli_linux_arm64

build-all: build-csgclaw build-csgclaw-cli
build-all: build-web
$(MAKE) build-server APP=csgclaw
$(MAKE) build-csgclaw-cli

run: build-csgclaw
run: build-server
$(BIN) serve

package:
Expand Down
28 changes: 23 additions & 5 deletions cli/serve/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ func TestServeForegroundOpensIMURLWhenBrowserAllowed(t *testing.T) {
}

run := testContext()
stdout := newNotifyingBuffer("Opened this URL in your browser.")
run.Stdout = stdout
cfg := config.Config{
Server: config.ServerConfig{
AdvertiseBaseURL: "http://example.test/base",
Expand All @@ -291,7 +293,12 @@ func TestServeForegroundOpensIMURLWhenBrowserAllowed(t *testing.T) {
if got, want := opened, "http://example.test/base/"; got != want {
t.Fatalf("OpenBrowser() URL = %q, want %q", got, want)
}
if got := run.Stdout.(*bytes.Buffer).String(); !strings.Contains(got, "Opened this URL in your browser.") {
select {
case <-stdout.Seen():
case <-time.After(time.Second):
t.Fatal("browser-open confirmation was not printed")
}
if got := stdout.String(); !strings.Contains(got, "Opened this URL in your browser.") {
t.Fatalf("stdout missing browser-open confirmation:\n%s", got)
}
}
Expand Down Expand Up @@ -1062,10 +1069,8 @@ default_registry = "team"
default_publish_registry = "local"

[[hub.registries]]
name = "team"
kind = "remote"
url = "https://hub.example.com"
token = "hu******et"
name = "builtin"
kind = "builtin"
enabled = true

[[hub.registries]]
Expand All @@ -1074,6 +1079,19 @@ kind = "local"
path = "/tmp/hub"
enabled = false

[[hub.registries]]
name = "official"
kind = "remote"
url = "https://csgclaw.opencsg.com"
enabled = true

[[hub.registries]]
name = "team"
kind = "remote"
url = "https://hub.example.com"
token = "hu******et"
enabled = true

[models]
default = "default.local.minimax-m2.5"

Expand Down
4 changes: 3 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ internal/sandbox/csghub/ CSGHub sandbox implementation
internal/im/ built-in csgclaw IM and PicoClaw bridge
internal/channel/ external channel integrations, including Feishu
internal/config/ config defaults, load/save
web/static/ shipped frontend assets
web/app/ Web UI development source and Vite project
web/static/ legacy frontend assets retained for comparison
web/static-dist/ generated Web UI assets for Go embed; run make build-web
```

`internal/bot` is the new business boundary for bot behavior. It should not be implemented as extra glue inside API handlers.
Expand Down
4 changes: 2 additions & 2 deletions docs/channel/csgclaw.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Notifier deliveries (GitLab/GitHub webhooks, and so on) to the CSGClaw Web IM us

### Related code paths

- Frontend parser/renderer: `web/static/app.js`
- Action-card test coverage: `web/static/app_action_card.test.cjs`
- Frontend parser/renderer: `web/app/src/components/business/MessageContent/MessageContent.tsx`, `web/app/src/components/business/MessageContent/structuredMessages.ts`
- Action-card and notifier-card test coverage: `web/app/tests/legacy-contract.test.ts`, `web/app/tests/components/MessageContent/structuredMessages.test.ts`
- Notifier card encoding: `internal/runtime/notifier/notify_card.go`, `internal/runtime/notifier/notify_webhooks.go`
- Feishu setup command output: `internal/templates/embed/runtimes/picoclaw/manager/workspace/skills/feishu/scripts/feishu_setup/csgclaw.py`
4 changes: 2 additions & 2 deletions docs/channel/csgclaw.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Notifier(GitLab/GitHub webhook 等)投递到 CSGClaw Web IM 时使用该类

### 相关代码路径

- 前端解析与渲染:`web/static/app.js`
- Action card 单测:`web/static/app_action_card.test.cjs`
- 前端解析与渲染:`web/app/src/components/business/MessageContent/MessageContent.tsx`、`web/app/src/components/business/MessageContent/structuredMessages.ts`
- Action card 与 Notifier card 单测:`web/app/tests/legacy-contract.test.ts`、`web/app/tests/components/MessageContent/structuredMessages.test.ts`
- Notifier 卡片生成:`internal/runtime/notifier/notify_card.go`、`internal/runtime/notifier/notify_webhooks.go`
- Feishu setup 命令输出:`internal/templates/embed/runtimes/picoclaw/manager/workspace/skills/feishu/scripts/feishu_setup/csgclaw.py`
Loading