Skip to content
Draft
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
44 changes: 44 additions & 0 deletions .github/workflows/the-unit-api-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Deploy the-unit/api to Cloud Run

on:
push:
branches: [main]
paths:
- "the-unit/api/**"
- "the-unit/scripts/deploy-gcp.sh"
- ".github/workflows/the-unit-api-deploy.yml"
workflow_dispatch:

env:
PROJECT_ID: temporal-frame-494501-p5
REGION: us-central1
SERVICE: gumption-api
SA_NAME: gumption-api

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4

- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_DEPLOY_SA }}

- name: Set up gcloud
uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ env.PROJECT_ID }}

- name: Deploy
run: bash the-unit/scripts/deploy-gcp.sh
env:
PROJECT_ID: ${{ env.PROJECT_ID }}
REGION: ${{ env.REGION }}
SERVICE: ${{ env.SERVICE }}
SA_NAME: ${{ env.SA_NAME }}
37 changes: 37 additions & 0 deletions the-unit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# THE UNIT — Daily Project Alert

Personal productivity scanner that walks your local `The_Unit_Assembly/`
directory and lists every `.py` / `.txt` file in each "Hub" subfolder that is
still pending review and finalization.

## Layout

```
the-unit/
├── unit_alert.py # The scanner script
└── The_Unit_Assembly/ # Master folder; one subfolder per Hub
├── Hub_Lead_Generation/
├── Hub_CRM_Automation/
└── Hub_Content_Engine/
```

Drop pasted AI-generated code into the appropriate Hub as `.py` or `.txt`
files. Anything sitting in a Hub is treated as "waiting to be finished".

## Usage

```bash
# From repo root
python3 the-unit/unit_alert.py

# Or point at a Unit Assembly folder living elsewhere
python3 the-unit/unit_alert.py --dir /path/to/The_Unit_Assembly
```

The script prints a daily-dated banner, lists pending files per Hub, and ends
with a total count plus a one-action nudge ("pick ONE file today").

## Tip

Add it to your morning routine alongside `norcal-toolkit/daily_workflow.py` so
both pipelines (sales follow-ups and code finalization) get a daily ping.
Empty file.
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions the-unit/api/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
dist
test
.git
.gitignore
*.log
.env
.env.*
README.md
5 changes: 5 additions & 0 deletions the-unit/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
*.log
.env
.env.*
18 changes: 18 additions & 0 deletions the-unit/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build && npm prune --omit=dev

FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=8080
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
EXPOSE 8080
USER node
CMD ["node", "dist/index.js"]
63 changes: 63 additions & 0 deletions the-unit/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# gumption-api — Cloud Run proxy

Serverless API proxy that holds provider keys server-side, gates every request
behind a **Firebase ID token**, and meters per-user daily token spend in
Firestore. Designed for the Gumption / Unit front-end.

## Routes

| Method + path | Upstream | Notes |
|---|---|---|
| `GET /healthz` | — | Public health check. |
| `POST /v1/anthropic/messages` | `api.anthropic.com/v1/messages` | Pure passthrough; usage parsed from response. |
| `POST /v1/openai/chat/completions` | `api.openai.com/v1/chat/completions` | Pure passthrough. |
| `POST /v1/xai/chat/completions` | `api.x.ai/v1/chat/completions` | Pure passthrough. |
| `POST /v1/gemini/:model:generateContent` | Vertex AI (`{REGION}-aiplatform.googleapis.com`) | Uses ADC from the attached service account — no API key. |

All `/v1/*` routes:

1. Require `Authorization: Bearer <firebase-id-token>`.
2. Check `gumption_usage/{uid}/days/{YYYY-MM-DD}.tokens < DAILY_TOKEN_CAP`.
3. Forward to the upstream.
4. Best-effort increment the user's daily token counter on success.

## Environment variables

Set on Cloud Run via `--set-env-vars` and `--set-secrets`:

| Var | Source | Required |
|---|---|---|
| `GCP_PROJECT` | env var | yes |
| `GCP_REGION` | env var | defaults to `us-central1` |
| `DAILY_TOKEN_CAP` | env var | defaults to `250000` |
| `ANTHROPIC_API_KEY` | Secret Manager: `anthropic-key:latest` | optional |
| `OPENAI_API_KEY` | Secret Manager: `openai-key:latest` | optional |
| `XAI_API_KEY` | Secret Manager: `xai-key:latest` | optional |

Vertex AI Gemini does not need a key — it uses the runtime SA's ADC.

## Local dev

```bash
cd the-unit/api
npm install
npm run typecheck
node --test --import tsx test/boot.test.ts # offline auth + 404 + health checks
```

To run the server locally against real upstreams you need:
- `GOOGLE_APPLICATION_CREDENTIALS` pointing at a SA key JSON file
- The same env vars as production set in a `.env` or your shell
- A Firebase ID token from a real user to test `/v1/*`

```bash
npm run dev # tsx watch on src/index.ts, port 8080
```

## Deploy to Cloud Run

See `the-unit/scripts/deploy-gcp.sh` (one-shot deploy) and
`the-unit/scripts/setup-secrets.sh` (one-time secret provisioning).

A push to `main` that touches `the-unit/api/**` also auto-deploys via
`.github/workflows/the-unit-api-deploy.yml`.
Loading