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
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Arcadia Science

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,11 @@ make py-test-integration
# Run API integration tests (requires Postgres).
make fe-test-integration
```

## License

Data Hub is released under the [MIT License](LICENSE). Copyright (c) 2026 Arcadia Science.

"Data Hub" and "Arcadia Science", along with related names and logos, are marks of
Arcadia Science. The MIT License covers the source code only and does not grant any
right to use these names or logos.
7 changes: 0 additions & 7 deletions developer-docs/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,6 @@ Environment-specific configuration is managed through environment variables, nev

Run `make check-all` before pushing. CI enforces the same checks.

## Environments

| Environment | API URL | S3 bucket |
| --- | --- | --- |
| Staging | `https://data-hub-env-staging-arcadia-science.vercel.app/api/v1` | `arcadia-data-hub-raw-staging` |
| Production | `https://data-hub.arcadiascience.com/api/v1` | `arcadia-data-hub-raw-production` |

## Testing

- Tests are co-located with each package: `lambda/tests/`, `watcher/tests/`, `packages/shared/tests/`.
Expand Down
2 changes: 1 addition & 1 deletion developer-docs/guides/installing-a-watcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ The watcher can't reach the Data Hub API. Check:

- Your internet connection.
- That the correct environment is set in the config (`staging`, `production`, or `preview`).
- That the API URL is reachable: `https://data-hub.arcadiascience.com` (production), `https://data-hub-env-staging-arcadia-science.vercel.app` (staging), or the custom URL you provided (preview).
- That the API URL is reachable.

### Files aren't being detected

Expand Down
10 changes: 5 additions & 5 deletions developer-docs/guides/managing-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The plaintext token is displayed once — **copy it immediately**. It cannot be
### Via the API

```sh
curl -X POST https://data-hub.arcadiascience.com/api/v1/tokens \
curl -X POST https://datahub.example.com/api/v1/tokens \
-H "Cookie: <session-cookie>" \
-H "Content-Type: application/json" \
-d '{"name": "FPLC watcher", "expires_at": "2027-01-01T00:00:00Z"}'
Expand Down Expand Up @@ -51,7 +51,7 @@ Add Data Hub to your MCP client configuration. For example, in Claude Desktop (`
{
"mcpServers": {
"data-hub": {
"url": "https://data-hub.arcadiascience.com/api/v1/mcp",
"url": "https://datahub.example.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer dhub_abc123..."
}
Expand All @@ -66,7 +66,7 @@ Or in Cursor (`.cursor/mcp.json`):
{
"mcpServers": {
"data-hub": {
"url": "https://data-hub.arcadiascience.com/api/v1/mcp",
"url": "https://datahub.example.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer dhub_abc123..."
}
Expand All @@ -82,7 +82,7 @@ See the [MCP server docs](../reference/mcp.md) for the full list of tools, resou
Pass the token in the `Authorization` header:

```sh
curl https://data-hub.arcadiascience.com/api/v1/instruments \
curl https://datahub.example.com/api/v1/instruments \
-H "Authorization: Bearer dhub_abc123..."
```

Expand Down Expand Up @@ -113,7 +113,7 @@ The token is immediately invalidated. Any watcher or client using it will start
### Via the API

```sh
curl -X DELETE https://data-hub.arcadiascience.com/api/v1/tokens/<token-id> \
curl -X DELETE https://datahub.example.com/api/v1/tokens/<token-id> \
-H "Cookie: <session-cookie>"
```

Expand Down
2 changes: 1 addition & 1 deletion developer-docs/ops/ci-and-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ cp infra/.env.example infra/.env.staging

```
ECR_IMAGE_URI=<image-uri-from-step-2>
DATA_HUB_API_URL=https://data-hub-env-staging-arcadia-science.vercel.app/api/v1
DATA_HUB_API_URL=https://datahub-staging.example.com/api/v1
DATA_HUB_API_KEY=<your-api-key>
GITHUB_OIDC_PROVIDER_ARN=<github-oidc-arn-from-step-1>
VERCEL_OIDC_PROVIDER_ARN=<vercel-oidc-arn-from-step-1>
Expand Down
8 changes: 4 additions & 4 deletions developer-docs/reference/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/
{
"mcpServers": {
"data-hub": {
"url": "https://data-hub.arcadiascience.com/api/v1/mcp",
"url": "https://datahub.example.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer dhub_abc123..."
}
Expand All @@ -39,7 +39,7 @@ Edit `.cursor/mcp.json` in your project or `~/.cursor/mcp.json` globally:
{
"mcpServers": {
"data-hub": {
"url": "https://data-hub.arcadiascience.com/api/v1/mcp",
"url": "https://datahub.example.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer dhub_abc123..."
}
Expand All @@ -54,7 +54,7 @@ Reload Cursor. Tools are invoked via the agent automatically when relevant.

The endpoint follows the MCP Streamable HTTP spec, so any compliant client works. Configure it with:

- **URL**: `https://data-hub.arcadiascience.com/api/v1/mcp`
- **URL**: `https://datahub.example.com/api/v1/mcp`
- **Transport**: Streamable HTTP (`GET` for the SSE stream, `POST` for client messages)
- **Auth header**: `Authorization: Bearer <your-token>`

Expand Down Expand Up @@ -146,7 +146,7 @@ The Bearer token is missing, mistyped, revoked, or expired. Verify the token at
- Confirm the server is listed under `mcpServers` in the client config.
- Check for JSON syntax errors in the config file.
- Restart the client after editing — most clients don't hot-reload MCP server definitions.
- Hit `https://data-hub.arcadiascience.com/api/v1/mcp` with `curl -H "Authorization: Bearer <token>"` to confirm the endpoint responds.
- Hit `https://datahub.example.com/api/v1/mcp` with `curl -H "Authorization: Bearer <token>"` to confirm the endpoint responds.

### `get_file_download_url` vs. `get_run_archive`

Expand Down
6 changes: 4 additions & 2 deletions infra/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ Resources:
- AllowedMethods:
- GET
- HEAD
# Modify this to include your web app domain.
AllowedOrigins:
- "https://data-hub.arcadiascience.com"
- "https://datahub.arcadiascience.com"
- "https://*.vercel.app"
- "http://localhost:3000"
AllowedHeaders:
Expand Down Expand Up @@ -191,8 +192,9 @@ Resources:
- AllowedMethods:
- GET
- HEAD
# Modify this to include your web app domain.
AllowedOrigins:
- "https://data-hub.arcadiascience.com"
- "https://datahub.arcadiascience.com"
- "https://*.vercel.app"
- "http://localhost:3000"
AllowedHeaders:
Expand Down
21 changes: 21 additions & 0 deletions lambda/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Arcadia Science

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
7 changes: 6 additions & 1 deletion lambda/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
name = "data-hub-lambda"
version = "0.2.0"
requires-python = ">=3.12"
license = "MIT"
license-files = ["LICENSE"]
authors = [
{ name = "Arcadia Science", email = "swe@arcadiascience.com" },
]
dependencies = [
"data-hub-shared",
"arcadia-microscopy-tools>=0.4.1",
Expand All @@ -19,7 +24,7 @@ dependencies = [
data-hub-process = "data_hub_lambda.cli:cli"

[build-system]
requires = ["setuptools>=61.0", "wheel"]
requires = ["setuptools>=77.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.uv.sources]
Expand Down
2 changes: 1 addition & 1 deletion lambda/src/data_hub_lambda/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(
timeout: tuple[float, float] = DEFAULT_TIMEOUT,
) -> None:
# base_url should include the API version prefix (e.g.,
# "https://data-hub.arcadiascience.com/api/v1") — method paths
# "https://datahub.example.com/api/v1") — method paths
# are appended relative to it.
self.base_url = base_url.rstrip("/")
self._timeout = timeout
Expand Down
21 changes: 21 additions & 0 deletions packages/shared/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Arcadia Science

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
7 changes: 6 additions & 1 deletion packages/shared/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
name = "data-hub-shared"
version = "0.1.0"
requires-python = ">=3.12"
license = "MIT"
license-files = ["LICENSE"]
authors = [
{ name = "Arcadia Science", email = "swe@arcadiascience.com" },
]
dependencies = [
"boto3>=1.35",
"requests>=2.31",
]

[build-system]
requires = ["setuptools>=61.0", "wheel"]
requires = ["setuptools>=77.0", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
Expand Down
2 changes: 1 addition & 1 deletion watcher/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# data-hub-watcher

A file-watcher agent that runs on lab instrument PCs and uploads new files to the [Arcadia Science Data Hub](https://github.com/Arcadia-Science/data-hub). It groups files into runs, retries uploads, sends heartbeats, and can optionally run as a Windows service.
A file-watcher agent that runs on lab instrument PCs and uploads new files to the [Data Hub](https://github.com/Arcadia-Science/data-hub). It groups files into runs, retries uploads, sends heartbeats, and can optionally run as a Windows service.

## Install

Expand Down
2 changes: 1 addition & 1 deletion watcher/tests/test_run_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def test_non_matching_file_returns_none(self, tmp_path: Path) -> None:
class TestRunIdExtractionWindows:
"""Verify that backslash paths are POSIX-normalized before matching."""

WATCH_DIR = r"D:\ArcadiaJOBS2023\JOBS_2023 Projects\Backup"
WATCH_DIR = r"D:\InstrumentData\JOBS_2023 Projects\Backup"

def test_filename_prefix(self) -> None:
pat = _preset_pattern("filename_prefix")
Expand Down
42 changes: 42 additions & 0 deletions web/app/api/v1/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { NextRequest } from "next/server";
import { authorize } from "@/lib/api/auth";
import { globalSearch, type SearchScope } from "@/lib/api/search";

// ---------------------------------------------------------------------------
// GET /api/v1/search?q=…&scope=all|runs|files|instruments
//
// Cross-entity global search powering the ⌘K palette. Returns grouped,
// relevance-ordered matches over runs, files, and instruments. There is no
// row-level scoping in Data Hub, so any caller with `runs:read` sees the same
// set the rest of the app exposes.
// ---------------------------------------------------------------------------

const VALID_SCOPES: ReadonlySet<SearchScope> = new Set([
"all",
"runs",
"files",
"instruments",
]);

function parseScope(raw: string | null): SearchScope {
return raw && VALID_SCOPES.has(raw as SearchScope)
? (raw as SearchScope)
: "all";
}

export async function GET(request: NextRequest) {
const authResult = await authorize(request, "runs:read");
if (authResult instanceof Response) {
return authResult;
}

const { searchParams } = request.nextUrl;
const query = searchParams.get("q") ?? "";
const scope = parseScope(searchParams.get("scope"));

// The builder itself returns an empty result below the minimum length, so
// the guard here just avoids the DB round-trip for 0–1 char queries.
const result = await globalSearch({ query, scope });

return Response.json(result);
}
12 changes: 9 additions & 3 deletions web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { NotificationBell } from "@/components/notifications/notification-bell";
import { NotificationsProvider } from "@/components/notifications/notifications-provider";
import { PreviewDeploymentBanner } from "@/components/preview-deployment-banner";
import { ArchiveDownloadProvider } from "@/components/runs/archive-download-provider";
import { SearchTrigger } from "@/components/search/search-trigger";
import { ThemeProvider } from "@/components/theme-provider";
import {
SIDEBAR_COOKIE_NAME,
Expand Down Expand Up @@ -145,9 +146,14 @@ export default async function RootLayout({
}}
/>
<SidebarInset className="pb-12">
<header className="flex h-12 shrink-0 items-center justify-between gap-2 px-4">
<SidebarTrigger />
<NotificationBell />
<header className="flex h-14 shrink-0 items-center justify-between gap-2 px-4">
<div className="flex h-8 items-center">
<SidebarTrigger />
</div>
<div className="flex h-8 items-center gap-2">
<SearchTrigger />
<NotificationBell />
</div>
</header>
{children}
</SidebarInset>
Expand Down
7 changes: 2 additions & 5 deletions web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@ import { dashboardParamsCache, hasActiveFilters } from "@/lib/search-params";

type DashboardParams = Awaited<ReturnType<typeof dashboardParamsCache.parse>>;

// `default: "Data Hub"` on the root metadata template already renders
// `<title>Data Hub</title>` here, so we skip an explicit `title` and
// override only the openGraph / twitter fields that need a strong
// `og:title` for link previews.
const description = "Instruments, runs, and watchers at Arcadia Science.";
const description =
"A central hub for your lab's instruments, runs, and files.";

export const metadata: Metadata = {
description,
Expand Down
Loading
Loading