Skip to content

Commit 07679ec

Browse files
committed
Merge remote-tracking branch 'origin/cocalc-api-20250927' into pr-20251208
2 parents 9561d97 + ef5d0ca commit 07679ec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+7309
-828
lines changed

.github/workflows/make-and-test.yml

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ jobs:
106106
pip install ipykernel
107107
python -m ipykernel install --prefix=./jupyter-local --name python3-local --display-name "Python 3 (Local)"
108108
109-
110109
- name: install pnpm
111110
uses: pnpm/action-setup@v4
112111
with:
@@ -128,6 +127,191 @@ jobs:
128127
name: "test-results-node-${{ matrix.node-version }}-pg-${{ matrix.pg-version }}"
129128
path: 'src/packages/*/junit.xml'
130129

130+
- name: Start CoCalc Hub
131+
run: |
132+
# Create conat password for hub internal authentication
133+
mkdir -p src/data/secrets
134+
echo "test-conat-password-$(date +%s)" > src/data/secrets/conat-password
135+
chmod 600 src/data/secrets/conat-password
136+
137+
cd src/packages/hub
138+
pnpm run hub-project-dev-nobuild > hub.log 2>&1 &
139+
HUB_PID=$!
140+
echo $HUB_PID > hub.pid
141+
echo "Hub started with PID $HUB_PID"
142+
# Check if process is still running after a moment
143+
sleep 2
144+
if ! kill -0 $HUB_PID 2>/dev/null; then
145+
echo "Error: Hub process died immediately after starting"
146+
echo "Hub log:"
147+
cat hub.log
148+
exit 1
149+
fi
150+
env:
151+
PGDATABASE: smc
152+
PGUSER: smc
153+
PGHOST: localhost
154+
COCALC_MODE: single-user
155+
COCALC_TEST_MODE: yes
156+
DEBUG: 'cocalc:*,-cocalc:silly:*,hub:*,project:*'
157+
158+
- name: Wait for hub readiness
159+
run: |
160+
MAX_ATTEMPTS=30
161+
READY=false
162+
for i in $(seq 1 $MAX_ATTEMPTS); do
163+
if curl -sf --max-time 3 http://localhost:5000/healthcheck > /dev/null; then
164+
echo "Hub is ready"
165+
READY=true
166+
break
167+
fi
168+
echo "Waiting for hub... ($i/$MAX_ATTEMPTS)"
169+
sleep 3
170+
done
171+
if [ "$READY" = "false" ]; then
172+
echo "Hub failed to become ready after $MAX_ATTEMPTS attempts"
173+
echo "Hub log:"
174+
cat src/packages/hub/hub.log || echo "No log file found"
175+
exit 1
176+
fi
177+
178+
- name: Create CI admin user and API key
179+
run: |
180+
cd src/packages/hub
181+
OUTPUT=$(node dist/run/test-create-admin.js)
182+
# Split output into account_id and API key (format: UUID;api-key)
183+
ACCOUNT_ID=$(echo "$OUTPUT" | cut -d';' -f1)
184+
API_KEY=$(echo "$OUTPUT" | cut -d';' -f2)
185+
186+
# Store in separate files
187+
echo "$ACCOUNT_ID" > ../../account_id.txt
188+
echo "$API_KEY" > ../../api_key.txt
189+
190+
# Validate account ID (UUID format)
191+
if ! echo "$ACCOUNT_ID" | grep -qE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'; then
192+
echo "Error: Invalid account ID format: $ACCOUNT_ID"
193+
exit 1
194+
fi
195+
196+
# Validate API key was created
197+
if [ ! -s ../../api_key.txt ]; then
198+
echo "Error: API key file is empty or missing"
199+
exit 1
200+
fi
201+
if ! echo "$API_KEY" | grep -qE '^sk-[A-Za-z0-9]+$'; then
202+
echo "Error: Invalid API key format: $API_KEY"
203+
exit 1
204+
fi
205+
206+
echo "Account ID: $ACCOUNT_ID"
207+
echo "API key created successfully"
208+
env:
209+
PGDATABASE: smc
210+
PGUSER: smc
211+
PGHOST: localhost
212+
213+
- name: Restart CoCalc Hub
214+
run: |
215+
HUB_PID=$(cat src/packages/hub/hub.pid)
216+
echo "Stopping hub with PID $HUB_PID"
217+
kill $HUB_PID || true
218+
# Wait a bit for graceful shutdown
219+
sleep 5
220+
# Force kill if still running
221+
kill -9 $HUB_PID 2>/dev/null || true
222+
223+
# Read the admin account ID created in the previous step
224+
cd src/packages/hub
225+
ADMIN_ACCOUNT_ID=$(cat ../../account_id.txt)
226+
echo "Using admin account ID: $ADMIN_ACCOUNT_ID"
227+
228+
# Export env vars for the hub process
229+
export COCALC_SETTING_JUPYTER_API_ENABLED=yes
230+
export COCALC_SETTING_JUPYTER_ACCOUNT_ID=$ADMIN_ACCOUNT_ID
231+
export COCALC_SETTING_JUPYTER_PROJECT_POOL_SIZE=1
232+
233+
# Start hub with new configuration
234+
pnpm run hub-project-dev-nobuild > hub.log 2>&1 &
235+
HUB_PID=$!
236+
echo $HUB_PID > hub.pid
237+
echo "Hub restarted with PID $HUB_PID"
238+
239+
# Check if process is still running after a moment
240+
sleep 2
241+
if ! kill -0 $HUB_PID 2>/dev/null; then
242+
echo "Error: Hub process died immediately after restarting"
243+
echo "Hub log:"
244+
cat hub.log
245+
exit 1
246+
fi
247+
248+
# Wait for hub to be ready
249+
MAX_ATTEMPTS=30
250+
READY=false
251+
for i in $(seq 1 $MAX_ATTEMPTS); do
252+
if curl -sf --max-time 3 http://localhost:5000/healthcheck > /dev/null; then
253+
echo "Hub is ready after restart"
254+
READY=true
255+
break
256+
fi
257+
echo "Waiting for hub to be ready... ($i/$MAX_ATTEMPTS)"
258+
sleep 3
259+
done
260+
if [ "$READY" = "false" ]; then
261+
echo "Hub failed to become ready after restart"
262+
echo "Hub log:"
263+
cat hub.log
264+
exit 1
265+
fi
266+
env:
267+
PGDATABASE: smc
268+
PGUSER: smc
269+
PGHOST: localhost
270+
COCALC_MODE: single-user
271+
COCALC_TEST_MODE: yes
272+
DEBUG: 'cocalc:*,-cocalc:silly:*,hub:*,project:*'
273+
274+
- name: Install uv for cocalc-api tests
275+
run: curl -LsSf https://astral.sh/uv/install.sh | sh && echo "$HOME/.local/bin" >> $GITHUB_PATH
276+
277+
- name: Run cocalc-api tests
278+
run: |
279+
export COCALC_API_KEY=$(cat src/api_key.txt)
280+
export COCALC_HOST=http://localhost:5000
281+
export CI=true
282+
cd src/python/cocalc-api && make ci
283+
env:
284+
PGDATABASE: smc
285+
PGUSER: smc
286+
PGHOST: localhost
287+
288+
- name: Stop CoCalc Hub
289+
if: always()
290+
run: |
291+
if [ -f src/packages/hub/hub.pid ]; then
292+
HUB_PID=$(cat src/packages/hub/hub.pid)
293+
echo "Stopping hub with PID $HUB_PID"
294+
kill $HUB_PID || true
295+
# Wait a bit for graceful shutdown
296+
sleep 5
297+
# Force kill if still running
298+
kill -9 $HUB_PID 2>/dev/null || true
299+
fi
300+
301+
- name: Upload hub logs
302+
uses: actions/upload-artifact@v4
303+
if: always()
304+
with:
305+
name: "hub-logs-node-${{ matrix.node-version }}-pg-${{ matrix.pg-version }}"
306+
path: 'src/packages/hub/hub.log'
307+
308+
- name: Upload cocalc-api test results
309+
uses: actions/upload-artifact@v4
310+
if: always()
311+
with:
312+
name: "cocalc-api-test-results-node-${{ matrix.node-version }}-pg-${{ matrix.pg-version }}"
313+
path: 'src/python/cocalc-api/test-results.xml'
314+
131315
report:
132316
runs-on: ubuntu-latest
133317

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ src/conf/tinc_hosts/
7171
# mocha: banket coverage report
7272
src/coverage
7373
src/*/coverage
74+
75+
# Python coverage files
76+
.coverage
77+
.coverage.*
78+
htmlcov/
79+
**/htmlcov/
7480
# comes up when testing in that directory
7581
src/rethinkdb_data/
7682
src/dev/project/rethinkdb_data/

src/.claude/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
"Bash(pnpm exec tsc:*)",
3636
"Bash(pnpm i18n:*)",
3737
"Bash(pnpm i18n:*:*)",
38+
"Bash(pnpm i18n:compile:*)",
39+
"Bash(pnpm i18n:download:*)",
40+
"Bash(pnpm i18n:extract:*)",
41+
"Bash(pnpm i18n:upload:*)",
3842
"Bash(pnpm i18n:update:*)",
3943
"Bash(pnpm info:*)",
4044
"Bash(pnpm list:*)",
@@ -49,9 +53,12 @@
4953
"Bash(prettier -w:*)",
5054
"Bash(psql:*)",
5155
"Bash(python3:*)",
56+
"Bash(uv:*)",
57+
"Bash(timeout:*)",
5258
"Bash(uv sync:*)",
5359
"WebFetch",
5460
"WebSearch",
61+
"mcp__cocalc__*",
5562
"mcp__cclsp__find_definition",
5663
"mcp__cclsp__find_references",
5764
"mcp__github__get_issue",

src/AGENTS.md

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,22 @@ CoCalc is organized as a monorepo with key packages:
105105
5. **Authentication**: Each conat request includes account_id and is subject to permission checks at the hub level
106106
6. **Subjects**: Messages are routed using hierarchical subjects like `hub.account.{uuid}.{service}` or `project.{uuid}.{compute_server_id}.{service}`
107107

108+
#### CoCalc Conat Hub API Architecture
109+
110+
**API Method Registration Pattern:**
111+
112+
- **Registry**: `packages/conat/hub/api/projects.ts` contains `export const projects = { methodName: authFirstRequireAccount }`
113+
- **Implementation**: `packages/server/conat/api/projects.ts` contains `export async function methodName() { ... }`
114+
- **Flow**: Python client `@api_method("projects.methodName")` → POST `/api/conat/hub``hubBridge()` → conat subject `hub.account.{account_id}.api` → registry lookup → implementation
115+
116+
**Example - projects.createProject:**
117+
118+
1. **Python**: `@api_method("projects.createProject")` decorator
119+
2. **HTTP**: `POST /api/conat/hub {"name": "projects.createProject", "args": [...]}`
120+
3. **Bridge**: `hubBridge()` routes to conat subject
121+
4. **Registry**: `packages/conat/hub/api/projects.ts: createProject: authFirstRequireAccount`
122+
5. **Implementation**: `packages/server/conat/api/projects.ts: export { createProject }``@cocalc/server/projects/create`
123+
108124
### Key Technologies
109125

110126
- **TypeScript**: Primary language for all new code
@@ -158,7 +174,6 @@ CoCalc is organized as a monorepo with key packages:
158174
- Prefix git commits with the package and general area. e.g. 'frontend/latex: ...' if it concerns latex editor changes in the packages/frontend/... code.
159175
- When pushing a new branch to Github, track it upstream. e.g. `git push --set-upstream origin feature-foo` for branch "feature-foo".
160176

161-
162177
## React-intl / Internationalization (i18n)
163178

164179
CoCalc uses react-intl for internationalization with SimpleLocalize as the translation platform.
@@ -216,11 +231,42 @@ Same flow as above, but **before 3. i18n:upload**, delete the key. Only new keys
216231
- Ignore everything in `node_modules` or `dist` directories
217232
- Ignore all files not tracked by Git, unless they are newly created files
218233

219-
# Important Instruction Reminders
234+
# CoCalc Python API Client Investigation
235+
236+
## Overview
237+
238+
The `python/cocalc-api/` directory contains a uv-based Python client library for the CoCalc API, published as the `cocalc-api` package on PyPI.
239+
240+
It also contains a test framework (`python/cocalc-api/tests/README.md`) and an MCP client (`python/cocalc-api/src/cocalc_api/mcp/README.md`).
241+
For convenience, a `python/cocalc-api/Makefile` exists.
242+
243+
## Client-Server Architecture Investigation
244+
245+
### API Call Flow
246+
247+
1. **cocalc-api Client** (Python) → HTTP POST requests
248+
2. **Next.js API Routes** (`/api/conat/{hub,project}`) → Bridge to conat messaging
249+
3. **ConatClient** (server-side) → NATS-like messaging protocol
250+
4. **Hub API Implementation** (`packages/conat/hub/api/`) → Actual business logic
251+
252+
### Endpoints Discovered
253+
254+
#### Hub API: `POST /api/conat/hub`
255+
256+
- **Bridge**: `packages/next/pages/api/conat/hub.ts``hubBridge()` → conat subject `hub.account.{account_id}.api`
257+
- **Implementation**: `packages/conat/hub/api/projects.ts`
258+
- **Available Methods**: `createProject`, `start`, `stop`, `setQuotas`, `addCollaborator`, `removeCollaborator`, etc.
259+
260+
#### Project API: `POST /api/conat/project`
261+
262+
- **Bridge**: `packages/next/pages/api/conat/project.ts``projectBridge()` → conat project subjects
263+
- **Implementation**: `packages/conat/project/api/` (system.ping, system.exec, system.jupyterExecute)
264+
265+
# important-instruction-reminders
220266

221-
- Do what has been asked; nothing more, nothing less
222-
- NEVER create files unless they're absolutely necessary for achieving your goal
223-
- ALWAYS prefer editing an existing file to creating a new one
224-
- REFUSE to modify files when the git repository is on the `master` or `main` branch
225-
- NEVER proactively create documentation files (`*.md`) or README files. Only create documentation files if explicitly requested by the User
226-
- when modifying a file with a copyright banner at the top, make sure to fix/add the current year to indicate the copyright year
267+
- Do what has been asked; nothing more, nothing less.
268+
- NEVER create files unless they're absolutely necessary for achieving your goal.
269+
- ALWAYS prefer editing an existing file to creating a new one.
270+
- NEVER proactively create documentation files (\*.md) or README files. Only create documentation files if explicitly requested by the User.
271+
- ALWAYS ask questions if something is unclear. Only proceed to the implementation step if you have no questions left.
272+
- When modifying a file with a copyright banner at the top, make sure to fix/add the current year to indicate the copyright year.

src/cocalc.code-workspace

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
{
44
"name": "cocalc",
55
"path": "."
6+
},
7+
{
8+
"path": "../.github"
69
}
710
],
811
"settings": {

src/packages/conat/hub/api/projects.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { authFirstRequireAccount } from "./util";
22
import { type CreateProjectOptions } from "@cocalc/util/db-schema/projects";
33
import { type UserCopyOptions } from "@cocalc/util/db-schema/projects";
4+
import {
5+
type ProjectState,
6+
type ProjectStatus,
7+
} from "@cocalc/util/db-schema/projects";
48

59
export const projects = {
610
createProject: authFirstRequireAccount,
@@ -12,6 +16,10 @@ export const projects = {
1216
setQuotas: authFirstRequireAccount,
1317
start: authFirstRequireAccount,
1418
stop: authFirstRequireAccount,
19+
deleteProject: authFirstRequireAccount,
20+
touch: authFirstRequireAccount,
21+
state: authFirstRequireAccount,
22+
status: authFirstRequireAccount,
1523
};
1624

1725
export type AddCollaborator =
@@ -103,4 +111,18 @@ export interface Projects {
103111

104112
start: (opts: { account_id: string; project_id: string }) => Promise<void>;
105113
stop: (opts: { account_id: string; project_id: string }) => Promise<void>;
114+
deleteProject: (opts: {
115+
account_id: string;
116+
project_id: string;
117+
}) => Promise<void>;
118+
119+
touch: (opts: { account_id: string; project_id: string }) => Promise<void>;
120+
state: (opts: {
121+
account_id: string;
122+
project_id: string;
123+
}) => Promise<ProjectState>;
124+
status: (opts: {
125+
account_id: string;
126+
project_id: string;
127+
}) => Promise<ProjectStatus>;
106128
}

src/packages/conat/hub/api/system.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { type UserSearchResult } from "@cocalc/util/db-schema/accounts";
99
export const system = {
1010
getCustomize: noAuth,
1111
ping: noAuth,
12+
test: authFirst,
1213
terminate: authFirst,
1314
userTracking: authFirst,
1415
logClientError: authFirst,
@@ -31,6 +32,8 @@ export interface System {
3132
getCustomize: (fields?: string[]) => Promise<Customize>;
3233
// ping server and get back the current time
3334
ping: () => { now: number };
35+
// test API key and return scope information (account_id) and server time
36+
test: () => Promise<{ account_id: string; server_time: number }>;
3437
// terminate a service:
3538
// - only admin can do this.
3639
// - useful for development

0 commit comments

Comments
 (0)