@meffecta/agent 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,197 @@
1
+ # How the agent is put together
2
+
3
+ Orientation for someone about to run a deployment. The set-up steps themselves are in
4
+ [IMPLEMENTATION.md](IMPLEMENTATION.md); this is what those steps are building and why.
5
+
6
+ ## The shape
7
+
8
+ ```
9
+ this engine repo ──CI──▶ ghcr.io/meffecta/agent (the image; never company-specific)
10
+
11
+ │ scripts/deploy.sh
12
+
13
+ your content repo ──cloned every run──▶ Cloud Run service (your project, your creds)
14
+ jobs/ worlds/ SYSTEM.md ↑
15
+ └─ credentials in Secret Manager
16
+ ```
17
+
18
+ The engine is a job runner: it wakes on a schedule (or a webhook, or new mail), clones the
19
+ content repo, and hands one job's prompt to the `claude` CLI with a working directory, a
20
+ system prompt, a set of skills and a memory directory. What the agent *does* is entirely
21
+ in the content repo and the credentials; the engine knows nothing about any company.
22
+
23
+ ## Two rules that explain most of the design
24
+
25
+ **The engine is generic; the content repo is yours.** Every run re-clones the content repo,
26
+ so a pushed prompt is live on the next run — no build, no deploy. Only moving to a new
27
+ *engine version* is a deploy. This is also why the engine repo's CI publishes an image and
28
+ stops: when a deployment takes a new version is that deployment's call.
29
+
30
+ **The agent's power is exactly its credentials.** There is no other lever. Give it a GCP
31
+ project of its own, and add one credential at a time — every skill degrades gracefully
32
+ when its variables are unset, so a half-configured agent is a working agent with fewer
33
+ abilities, not a broken one. Reviewing what an agent can do means reviewing what its
34
+ service account is invited to and what tokens sit in its Secret Manager.
35
+
36
+ ## What needs what
37
+
38
+ | Change | Effect |
39
+ | --- | --- |
40
+ | Job prompts, run settings, `SYSTEM.md`, worlds, content-repo skills | **Push** — live on the next run |
41
+ | Adding, renaming, or retriggering a job (`cron`/`webhook`/`inbox` frontmatter) | **Restart** — triggers register from a boot-time clone, plus `scripts/setup-scheduler.sh` on Cloud Run |
42
+ | A new engine version | `scripts/deploy.sh` (which re-syncs the triggers too) |
43
+
44
+ Renaming a job also moves its identity: memory lives at `<MEMORY_DIR>/jobs/<name>/`, so
45
+ copy that directory across or the job wakes up amnesiac, and pending spawns for the old
46
+ name die with it.
47
+
48
+ ## Operating a deployment
49
+
50
+ Operating a deployment is one command-line tool, **`npx @meffecta/agent`**, published to
51
+ npm alongside the engine image it deploys. Installing it is all the access an operator
52
+ needs; the engine's source is not involved. Each release of the tool records the engine
53
+ build it was published with, so upgrading the tool is how you move to a new engine —
54
+ deliberately, rather than by surprise.
55
+
56
+ It reads this deployment's identity from a `deployment.env` in your content repo, committed
57
+ beside `jobs/`. Which GCP project a set of jobs runs as is a fact about that deployment,
58
+ and none of it is secret.
59
+
60
+ That makes the working shape: stand in the content repo and run the tool.
61
+
62
+ ```bash
63
+ cd /path/to/acme-agent-home
64
+ npx @meffecta/agent status
65
+ npx @meffecta/agent deploy
66
+ ```
67
+
68
+ Nothing is read from exported shell variables, so a command cannot silently act on the
69
+ wrong deployment; each script prints the project and service it is about to touch, and
70
+ refuses to run when it cannot find them (`--config <file>` overrides the lookup).
71
+
72
+ `deployment.env` also holds the **runtime shape** — `SCALING`, `CPU`, `MEMORY`,
73
+ `MAX_INSTANCES`, `REQUEST_TIMEOUT` — which `deploy.sh` re-asserts on every rollout. So the
74
+ file, not the console, is the truth about how the service runs, and `SCALING` in particular
75
+ has to stay honest: it and the triggers are one decision made in two places.
76
+
77
+ Every command is one of the scripts below with a name in front of it — the CLI is the
78
+ interface, the scripts are the implementation, and they are published together so the pair
79
+ cannot drift. A deployment without npm can extract the scripts from the public image
80
+ instead and run them directly.
81
+
82
+ | Command / script | Does |
83
+ | --- | --- |
84
+ | `setup-infrastructure.sh` | one-time GCP provisioning; idempotent |
85
+ | `setup-scheduler.sh` | the deployment's external triggers — Cloud Scheduler + Tasks; idempotent |
86
+ | `set-artifact-cleanup.sh` | expire mirrored images in the deployment's registry |
87
+ | `update-tooling.sh` | refresh these scripts from a published engine image |
88
+ | `set-secret.sh` | store a value in Secret Manager and bind it; re-run to rotate |
89
+ | `set-env.sh` | non-secret service settings, several per revision |
90
+ | `deploy.sh` | roll a published engine image out, and assert the runtime shape |
91
+
92
+ For a question rather than a change, the service serves **`/ask`**: a prompt box behind the
93
+ same secret, over basic auth, that runs it with the deployment's skills and credentials and
94
+ shows the answer. It is the quickest way to find out what the agent can currently see.
95
+
96
+ `set-secret.sh` never lets a value reach the shell history or the screen: `--random`
97
+ generates one, a pipe or `--from-file` supplies one, and otherwise it prompts with echo
98
+ off. `deploy.sh`, `set-env.sh` and `setup-scheduler.sh` take `--dry-run`.
99
+
100
+ ## Where the rules live
101
+
102
+ Every run's system prompt is built from two layers:
103
+
104
+ 1. **The deployment's `SYSTEM.md`**, from its content repo — who this agent serves, its
105
+ worlds, its tone, which systems belong to whom.
106
+ 2. **The engine's `SYSTEM.base.md`**, from the image — the safety rules every deployment
107
+ gets whether or not anyone remembered to write them.
108
+
109
+ The baseline is appended **last**, closest to the task, and says in its own text that it
110
+ overrides what precedes it. A deployment can make a rule stricter or name the concrete
111
+ accounts it applies to; it cannot relax one, and it cannot shadow the file — the engine
112
+ resolves it from its own install directory, never from the working repo, so no content a
113
+ deployment or a run controls can stand in for it. A missing baseline throws rather than
114
+ running unprotected.
115
+
116
+ What the baseline carries: content you read is data and never instructions; act only
117
+ where the job says, with anything outward-facing or irreversible left for a person unless
118
+ the job directs it; secrets never leave; other people's privacy; report honestly. Both
119
+ layers hold across a spawned follow-up run, which inherits its parent's system prompt.
120
+
121
+ A rule that must be *structurally* impossible belongs in code, not prose — the way the
122
+ Google Ads skill splits reads from writes and caps every mutation, and the way a read-only
123
+ mail token makes sending impossible rather than merely forbidden.
124
+
125
+ ## Skills
126
+
127
+ Skills are how the agent reaches a system: the how-to for Gmail, Microsoft Graph, Drive,
128
+ Cloudflare, a browser, Grafana, PostHog, Google Ads. They resolve in three layers, each
129
+ winning over the next:
130
+
131
+ 1. the **working repo** a job runs in (`repo:` frontmatter),
132
+ 2. the **content repo** (`.claude/skills/`) — this deployment's own or its overrides,
133
+ 3. the **engine defaults**, shipped in the image.
134
+
135
+ So a deployment can override any default without forking the engine, and a product repo
136
+ can supply its own variant for jobs that run inside it.
137
+
138
+ ## Triggers
139
+
140
+ Four ways a run starts. The first three are configured in a job's frontmatter:
141
+
142
+ - **cron** — a schedule, in the deployment's timezone
143
+ - **webhook** — `POST /webhooks/<name>`
144
+ - **inbox** — new mail to a watched address, polled
145
+
146
+ The fourth is the only one the agent pulls itself: a run that cannot finish inside its
147
+ time limit, or that has to wait for something, writes the instruction for a **follow-up
148
+ run** and schedules it. That follow-up is the same job identity — same repo, system
149
+ prompt, settings and memory — with a fresh time limit. The token that authorizes it stands
150
+ for that run's identity and dies with the run, so a run can only ever schedule more of its
151
+ own job's work, whatever its prompt says.
152
+
153
+ ### What actually wakes the service
154
+
155
+ On Cloud Run the service **scales to zero and is billed only while a request is open**,
156
+ which is why an agent that is busy half an hour a day does not cost a machine's worth a
157
+ month. The price of that is a service with no CPU of its own: it cannot keep a clock, and
158
+ it cannot finish work in the background of a response. So all four triggers are delivered
159
+ to it as HTTP requests, and the handler holds each request open until the run settles.
160
+
161
+ `scripts/setup-scheduler.sh` creates the senders: **Cloud Scheduler** for the crons and for
162
+ an hourly (or poll-interval) housekeeping sweep, **Cloud Tasks** for everything that has to
163
+ be acknowledged immediately — webhooks, manual runs, follow-ups due later. Both cap one
164
+ attempt at 30 minutes, so a run that lasts longer spans several: the handler answers 503,
165
+ and the retry re-attaches to the same run instead of starting another. Nothing about
166
+ durability rests on this — the queue journal and the spawn spool on the memory bucket stay
167
+ the record, and the sweep re-drives anything a delivery lost.
168
+
169
+ The alternative shape is a machine that is always awake — a Mac mini, or a service pinned
170
+ to `min-instances=1`. There the process keeps its own timers and needs none of the above;
171
+ leaving `AGENT_TASKS_QUEUE` unset is what selects it. Do not do half of each: a
172
+ scaled-to-zero service without Scheduler jobs looks healthy and runs nothing, and an
173
+ always-on service with them fires every cron twice.
174
+
175
+ ## Memory and audit
176
+
177
+ Each job has a memory directory it reads at the start of a run and updates at the end —
178
+ working state, not bulk content. It lives on a mounted bucket, so it survives restarts and
179
+ deploys.
180
+
181
+ Separately, every run writes to an **audit bucket** that is never mounted and never
182
+ readable by the agent: the prompt, the trigger, the result, and the full transcript of
183
+ every tool call. That is where you look when a run surprises you, and it is deliberately
184
+ out of the agent's own reach.
185
+
186
+ ## Where it runs
187
+
188
+ Cloud Run is the default and the only place where service-account authentication works for
189
+ the metadata-server surfaces (domain-wide delegation, GA4, Search Console, Ads, deployment
190
+ listing). The same image runs anywhere docker does, and the service also runs natively on
191
+ a Mac or Linux box with Node 24+, `MEMORY_DIR` pointed at a real directory and
192
+ `NODE_ENV=production` to enable crons — keep it alive with launchd or systemd. Off Cloud
193
+ Run, the metadata-server surfaces degrade to their fallbacks.
194
+
195
+ **Run it in exactly one place at a time.** Two instances mean every cron fires twice and
196
+ both send email — and on Cloud Run, scaling to zero is what makes the second copy tempting,
197
+ so check that the first one is actually stopped rather than merely idle.
@@ -0,0 +1,292 @@
1
+ # Setting up a new agent
2
+
3
+ Every step from nothing to a first run, in order. For how the pieces fit and why, see
4
+ [ARCHITECTURE.md](ARCHITECTURE.md).
5
+
6
+ Everything here is one command-line tool, `npx @meffecta/agent`, run from **your content
7
+ repo** — the directory step 3 creates, which holds this deployment's jobs and its settings.
8
+ You never need the engine's source: the tool and the engine image it deploys are published
9
+ together, and installing the tool is all the access you need.
10
+
11
+ npx @meffecta/agent steps # this guide, condensed to the command for each step
12
+
13
+ ---
14
+
15
+ ## 1. Create a GCP project
16
+
17
+ Give the agent a project of its own, separate from any product project it will read: this
18
+ project's service account gets project-wide secret access, and the mail tokens stored here
19
+ must not be readable by another system's machinery.
20
+
21
+ ```bash
22
+ gcloud auth login
23
+ npx @meffecta/agent create-project
24
+ ```
25
+
26
+ It asks for a display name, suggests a project ID from it, validates the ID against
27
+ Google's rules before spending a round trip, and creates the project. Pass
28
+ `--name`/`--id` to skip the prompts, or `ORGANIZATION=…` / `FOLDER=…` to place it.
29
+
30
+ - [ ] Note the **project ID** it prints. Project IDs are globally unique, so a plain name
31
+ is often taken and you end up with a suffixed one (`acme-agent-506513`). The id, not
32
+ the display name, is what every later command needs.
33
+
34
+ ## 2. Link a billing account
35
+
36
+ Nothing else works without this: provisioning starts by enabling APIs, which fails on an
37
+ unbilled project.
38
+
39
+ ```bash
40
+ npx @meffecta/agent link-billing --project <PROJECT_ID>
41
+ ```
42
+
43
+ It lists the billing accounts you can use, marks closed ones as unselectable (linking one
44
+ appears to succeed and leaves the project unbilled), links your choice, and then confirms
45
+ the project really is billed rather than trusting the command's exit code.
46
+
47
+ - [ ] `billingEnabled: true`
48
+
49
+ If it reports a **billing quota**, that is the account's cap on how many projects it may
50
+ fund, not a problem with this project — free a slot with `gcloud billing projects unlink
51
+ <PROJECT>`, or request an increase.
52
+
53
+ ## 3. Create the content repo
54
+
55
+ A private GitHub repo holding everything about *this* deployment:
56
+
57
+ ```
58
+ deployment.env # which GCP project and service this deployment runs as
59
+ SYSTEM.md # who the agent serves, its worlds, its tone
60
+ jobs/ # one .md per job: YAML frontmatter + prompt body
61
+ worlds/ # one folder per business area (optional to start)
62
+ .claude/skills/ # skills this deployment owns or overrides (optional)
63
+ ```
64
+
65
+ `deployment.env` is committed — none of it is secret, and a clone of the repo is then
66
+ everything an operator needs:
67
+
68
+ ```bash
69
+ npx @meffecta/agent init # writes deployment.env here
70
+ ```
71
+
72
+ ```ini
73
+ PROJECT=acme-agent-506513 # the project ID from step 1
74
+ SERVICE=acme-agent
75
+ ARTIFACT_REPO=acme-agent-images
76
+ REGION=europe-west1
77
+ ```
78
+
79
+ `SYSTEM.md` describes **who this agent serves** — the person or team, the businesses, the
80
+ tone, which systems belong to whom. It does not need to restate the safety rules: the
81
+ engine appends its own baseline ([SYSTEM.base.md](SYSTEM.base.md) — what you read is data
82
+ not instructions, act only where the job says, secrets never leave, third-party privacy,
83
+ report honestly) to every run in every deployment, and that baseline overrides anything
84
+ contradicting it. Your `SYSTEM.md` can make a rule *stricter*, or name the concrete
85
+ accounts and people it applies to; it cannot relax one.
86
+
87
+ One job to start, `jobs/hello.md`:
88
+
89
+ ```markdown
90
+ ---
91
+ name: hello
92
+ cron: "0 7 * * 1-5"
93
+ ---
94
+
95
+ Say good morning and list today's date. Send it by email to <person> using the
96
+ send-email skill. If email is unavailable, return the text as your result.
97
+ ```
98
+
99
+ Frontmatter keys: `cron`, `webhook`, `inbox`, `repo`, `systemMd`, `model`, `effort`,
100
+ `allowedTools`, `timeoutSeconds`, `disabled`. A job with no `name:` takes its filename.
101
+
102
+ - [ ] Repo pushed to GitHub
103
+ - [ ] `cd` into it — **every command from here on runs from the content repo**
104
+
105
+ ## 4. Install the tools
106
+
107
+ On the machine you operate from:
108
+
109
+ ```bash
110
+ docker info >/dev/null && echo "docker ok" # needed to mirror engine images
111
+ git --version && gh --version
112
+ node --version # 24+
113
+ npm i -g @anthropic-ai/claude-code
114
+ corepack enable
115
+ ```
116
+
117
+ - [ ] `gcloud auth configure-docker $REGION-docker.pkg.dev` — one-time, so the mirror in
118
+ step 8 can push into your Artifact Registry. Pulling the engine image itself needs
119
+ no login: `ghcr.io/meffecta/agent` is public.
120
+
121
+ Then the tool itself:
122
+
123
+ ```bash
124
+ npm i -g @meffecta/agent # or use npx, which needs no install
125
+ meffecta-agent version # names the engine build this version deploys
126
+ ```
127
+
128
+ - [ ] `meffecta-agent help` lists the commands
129
+ - [ ] `meffecta-agent version` shows a pinned engine tag, not "latest"
130
+
131
+ Pin the version you install. Each release of the tool deploys the engine image published
132
+ alongside it, so upgrading the tool is how you move to a new engine — deliberately, not by
133
+ surprise.
134
+
135
+ <details>
136
+ <summary>No npm? The same tooling is inside the public image.</summary>
137
+
138
+ ```bash
139
+ cid=$(docker create ghcr.io/meffecta/agent:latest) \
140
+ && docker cp "$cid:/app/scripts" . \
141
+ && docker rm "$cid" >/dev/null
142
+ ```
143
+
144
+ That drops `scripts/` into your content repo; run `scripts/deploy.sh` and friends directly,
145
+ and `scripts/update-tooling.sh --tag <the tag you deploy>` to refresh them. Every CLI
146
+ command is one of these scripts with a name in front of it.
147
+ </details>
148
+
149
+ ## 5. Mint the credentials
150
+
151
+ ```bash
152
+ claude setup-token # → CLAUDE_CODE_OAUTH_TOKEN, pasted in step 7
153
+ ```
154
+
155
+ - [ ] `CLAUDE_CODE_OAUTH_TOKEN` — this deployment bills its own runs
156
+ - [ ] `GITHUB_TOKEN` — fine-grained PAT with **contents read on the content repo** (the
157
+ service cannot start without it), plus write on any repo whose jobs open PRs
158
+ - [ ] `AGENT_WEBHOOK_SECRET` needs nothing here — step 7 generates it
159
+
160
+ **Gmail + Calendar** (skip if the agent needs no mailbox). In the agent's GCP project:
161
+ enable the Gmail and Calendar APIs; OAuth consent screen → **External**, then **publish to
162
+ production** — an app left in "Testing" expires refresh tokens after 7 days; create an
163
+ OAuth client of type **Desktop app**. Then per account:
164
+
165
+ ```bash
166
+ npx @meffecta/agent mint-gmail \
167
+ --client-id <id> --client-secret <secret> [--account <NAME>] [--readonly]
168
+ ```
169
+
170
+ Sign in as the account the token is for. Default → `GMAIL_REFRESH_TOKEN`; named →
171
+ `GMAIL_<NAME>_REFRESH_TOKEN`. Use `--readonly` for any mailbox this company does not
172
+ administer alone.
173
+
174
+ **Microsoft 365** (only if a mailbox lives there): one Entra app registration, then
175
+ `npx @meffecta/agent mint-graph --account <NAME> …`.
176
+
177
+ ## 6. Provision the infrastructure
178
+
179
+ ```bash
180
+ npx @meffecta/agent setup-infra
181
+ ```
182
+
183
+ Idempotent — re-run it after any change. It prints the project and service it is about to
184
+ touch, then creates: the APIs, an Artifact Registry repo, the runtime service account, a
185
+ memory bucket (mounted at `/memory`), an audit bucket (365-day retention, write-once,
186
+ never mounted), and the Cloud Run service shell with a placeholder image and CPU always
187
+ allocated.
188
+
189
+ - [ ] The banner names the project from your `deployment.env` — if not, you are in the
190
+ wrong directory
191
+
192
+ ## 7. Wire up the configuration
193
+
194
+ `set-secret.sh` stores a value in Secret Manager and binds it to the service in one step;
195
+ the value never reaches your shell history or the screen. Re-running it rotates a value.
196
+
197
+ ```bash
198
+ npx @meffecta/agent set-secret AGENT_WEBHOOK_SECRET --random # generated for you
199
+ npx @meffecta/agent set-secret CLAUDE_CODE_OAUTH_TOKEN # prompts, hidden
200
+ npx @meffecta/agent set-secret GITHUB_TOKEN
201
+ ```
202
+
203
+ - [ ] `AGENT_WEBHOOK_SECRET`
204
+ - [ ] `CLAUDE_CODE_OAUTH_TOKEN`
205
+ - [ ] `GITHUB_TOKEN`
206
+
207
+ Non-secret settings go through `set-env` (several at once become one revision; add
208
+ `--dry-run` to see what it would do). **The service will not boot without `GIT_REPO_URL`:**
209
+
210
+ ```bash
211
+ npx @meffecta/agent set-env GIT_REPO_URL=https://github.com/<owner>/<repo>.git
212
+ ```
213
+
214
+ - [ ] `GIT_REPO_URL` points at the content repo from step 3
215
+
216
+ Optional, one ability each: `GMAIL_*`, `MSGRAPH_*`, `GRAFANA_*`, `CLOUDFLARE_*`,
217
+ `POSTHOG_*`, `GOOGLE_ADS_*`, `AHREFS_API_KEY`, `<WORLD>_GA4_PROPERTY_ID`. Full list with
218
+ comments in the engine repo's `.env.example`. Tokens and keys go through `set-secret`;
219
+ addresses, ids and URLs through `set-env`. `meffecta-agent env` and `meffecta-agent secrets`
220
+ show what is set — the second never prints a value.
221
+
222
+ Surfaces that authenticate as the **runtime service account** rather than a token — GA4,
223
+ Search Console, Google Ads, Cloud Run deployment listing, and delegated Gmail/Drive/
224
+ Calendar — additionally need that service account invited on each property, and
225
+ domain-wide delegation granted in the Workspace admin console. Leave these for last; each
226
+ degrades gracefully until granted.
227
+
228
+ ## 8. Deploy the engine
229
+
230
+ ```bash
231
+ npx @meffecta/agent deploy # --dry-run first to see the target
232
+ ```
233
+
234
+ Mirrors the engine image this tool version was published with into your Artifact Registry,
235
+ rolls it out, and asserts the runtime shape from `deployment.env`. `--tag <other>` pins or
236
+ rolls back to a different build.
237
+
238
+ ## 9. Create the triggers
239
+
240
+ The service scales to zero and has CPU only while it is answering a request, so nothing in
241
+ it fires on its own. This is what sends it work:
242
+
243
+ ```bash
244
+ npx @meffecta/agent setup-scheduler # --dry-run first to see what it would create
245
+ ```
246
+
247
+ It creates a Cloud Tasks queue, points the service at it, and creates one Cloud Scheduler
248
+ job per `cron:` in your content repo plus a housekeeping sweep. It reads the job list from
249
+ the running service, so step 8 has to have succeeded first.
250
+
251
+ - [ ] It reports one trigger per cron job, plus `<service>-sweep`
252
+ - [ ] `npx @meffecta/agent triggers` agrees, and shows the task queue running
253
+
254
+ Re-run it whenever a job's `cron:` changes or a job is added or renamed — `deploy` does it
255
+ for you on every rollout from here on. Check it any time with `meffecta-agent triggers`.
256
+
257
+ ## 10. Verify
258
+
259
+ ```bash
260
+ npx @meffecta/agent status
261
+ npx @meffecta/agent jobs
262
+ ```
263
+
264
+ - [ ] `status` shows a ready revision, your triggers, and an idle queue
265
+ - [ ] `jobs` lists your jobs — this proves the boot clone worked. An empty list means
266
+ `GIT_REPO_URL` is wrong or `GITHUB_TOKEN` cannot clone it.
267
+
268
+ Then exercise every configured credential from inside the deployment:
269
+
270
+ ```bash
271
+ npx @meffecta/agent ask "run node /app/scripts/verify-credentials.mjs and return its output"
272
+ ```
273
+
274
+ - [ ] Each surface reports ok or skip; no FAIL (`skip` = not configured, which is fine)
275
+
276
+ The same thing has a web page, for anyone who would rather not use a terminal: open
277
+ `/ask` on the service URL that `status` printed. Any username, the secret as the password.
278
+
279
+ - [ ] The page loads and an answer comes back
280
+
281
+ ## 11. First real run
282
+
283
+ ```bash
284
+ npx @meffecta/agent run hello
285
+ npx @meffecta/agent logs --limit 50
286
+ ```
287
+
288
+ - [ ] Read what it produced before letting the cron take over
289
+
290
+ Then add jobs one at a time — each push is live on the next run. `disabled: true` in
291
+ frontmatter is the kill switch, effective on push. Every prompt, result and full
292
+ transcript lands in the audit bucket.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @meffecta/agent
2
+
3
+ Set up and operate a **Meffecta Agent** deployment — a self-hosted Claude Code job runner
4
+ that wakes on a schedule, a webhook, or new mail, and runs a job against a fresh clone of
5
+ your own content repo.
6
+
7
+ The agent itself is a container image (`ghcr.io/meffecta/agent`). This package is the tool
8
+ that stands one up and runs it, and it is all you need: the engine's source is private,
9
+ and nothing here depends on it.
10
+
11
+ ```bash
12
+ npx @meffecta/agent steps # the whole set-up, in order
13
+ npx @meffecta/agent help # every command
14
+ ```
15
+
16
+ ## What it does
17
+
18
+ Run it from your **content repo** — the directory holding `deployment.env`, `jobs/`,
19
+ `worlds/` and `SYSTEM.md`. That file says which GCP project and Cloud Run service this
20
+ deployment is, so a command can never quietly act on the wrong one.
21
+
22
+ ```bash
23
+ npx @meffecta/agent create-project # a GCP project
24
+ npx @meffecta/agent link-billing # attach billing
25
+ npx @meffecta/agent setup-infra # registry, buckets, service account, service
26
+ npx @meffecta/agent set-secret AGENT_WEBHOOK_SECRET --random
27
+ npx @meffecta/agent set-env GIT_REPO_URL=https://github.com/you/your-content-repo.git
28
+ npx @meffecta/agent deploy # roll out the engine
29
+ npx @meffecta/agent setup-scheduler # give it its triggers
30
+ ```
31
+
32
+ Then, day to day:
33
+
34
+ ```bash
35
+ npx @meffecta/agent status # revision, shape, triggers, queue
36
+ npx @meffecta/agent jobs # what is registered, and what triggers it
37
+ npx @meffecta/agent env # every setting (secret values never printed)
38
+ npx @meffecta/agent secrets # what is stored, and what the service reads
39
+ npx @meffecta/agent triggers # the Cloud Scheduler jobs and task queue
40
+ npx @meffecta/agent logs --limit 100
41
+ npx @meffecta/agent run morning-brief
42
+ npx @meffecta/agent ask "how did search do last week"
43
+ ```
44
+
45
+ ## Two things worth knowing
46
+
47
+ **The version you install is the engine you deploy.** Each release records the engine build
48
+ published alongside it, and `deploy` defaults to that image. Tooling and engine move
49
+ together, so a script can never provision the wrong shape for the engine it is aimed at.
50
+ `meffecta-agent version` tells you which pair you have.
51
+
52
+ **Secret values are never printed.** `secrets` lists what exists and whether the service
53
+ reads it; `env` names secret-backed settings without resolving them. Setting one takes the
54
+ value from a pipe, a file, or a hidden prompt — never from your shell history.
55
+
56
+ ## Requirements
57
+
58
+ `gcloud` (authenticated), `docker` (to mirror engine images into your registry), `git`,
59
+ and Node 20+. macOS or Linux.
60
+
61
+ ## Documentation
62
+
63
+ `IMPLEMENTATION.md` and `ARCHITECTURE.md` ship inside this package — the full set-up, and
64
+ how the pieces fit.
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ import { COMMANDS, cliVersion, engineTag, GROUPS } from "../lib/commands.js";
3
+ import { loadDeployment, UserError } from "../lib/config.js";
4
+
5
+ /**
6
+ * One entry point for everything an operator does to a Meffecta Agent deployment.
7
+ *
8
+ * The engine itself is a container image; this is the tool that stands one up and runs it.
9
+ * Most commands are the shipped shell scripts with a name in front of them — the CLI is
10
+ * the interface, those scripts are the implementation, and they travel together in this
11
+ * package so the pair cannot drift.
12
+ *
13
+ * No runtime dependencies, on purpose: `npx @meffecta/agent` should be one download, and
14
+ * a tool that holds someone's cloud credentials is a poor place for a supply chain.
15
+ */
16
+
17
+ const STEPS = [
18
+ ["create-project", "A new GCP project."],
19
+ ["link-billing", "Attach a billing account, or nothing can run."],
20
+ ["", "Create your content repo — jobs/, worlds/, SYSTEM.md, deployment.env."],
21
+ ["", "Install gcloud, docker, git, node 24+, and the claude CLI."],
22
+ ["mint-gmail", "Credentials: claude setup-token, then a token per mail account."],
23
+ ["setup-infra", "Provision GCP and the Cloud Run service shell."],
24
+ ["set-secret", "AGENT_WEBHOOK_SECRET --random, CLAUDE_CODE_OAUTH_TOKEN, GITHUB_TOKEN."],
25
+ ["set-env", "GIT_REPO_URL=… — the service will not boot without it."],
26
+ ["deploy", "Roll out the engine image."],
27
+ ["setup-scheduler", "Give it its triggers. Scaled to zero, it fires nothing without them."],
28
+ ["status", 'Check it. Then: jobs, run <job>, ask "…".'],
29
+ ];
30
+
31
+ function help() {
32
+ const { tag, pinned } = engineTag();
33
+ console.log("meffecta-agent — set up and operate a Meffecta Agent deployment\n");
34
+ console.log("Usage: meffecta-agent <command> [options]");
35
+ console.log(" npx @meffecta/agent <command> [options]\n");
36
+ console.log("Run it from your content repo — the directory holding deployment.env and jobs/.\n");
37
+ for (const group of GROUPS) {
38
+ console.log(`${group.title}:`);
39
+ const width = Math.max(...group.commands.map(([name]) => name.length));
40
+ for (const [name, summary] of group.commands) {
41
+ console.log(` ${name.padEnd(width)} ${summary}`);
42
+ }
43
+ console.log("");
44
+ }
45
+ console.log(" steps The whole set-up, in order, with the command for each");
46
+ console.log(" version This CLI and the engine build it deploys\n");
47
+ console.log("Every command takes --help, and passes its options through to the underlying");
48
+ console.log("script — so `meffecta-agent deploy --dry-run` and `--tag <sha>` work as documented.\n");
49
+ console.log(`Engine: ghcr.io/meffecta/agent:${tag}${pinned ? " (pinned to this release)" : ""}`);
50
+ }
51
+
52
+ function steps() {
53
+ console.log("Setting up a deployment, in order. The full text is IMPLEMENTATION.md.\n");
54
+ STEPS.forEach(([command, note], index) => {
55
+ const label = `${String(index + 1).padStart(2)}.`;
56
+ console.log(`${label} ${note}`);
57
+ if (command) {
58
+ console.log(` meffecta-agent ${command}`);
59
+ }
60
+ console.log("");
61
+ });
62
+ console.log("Each step is checkable: `meffecta-agent status` after the last one should show");
63
+ console.log("your triggers, and `meffecta-agent jobs` your jobs.");
64
+ }
65
+
66
+ function version() {
67
+ const { tag, pinned } = engineTag();
68
+ const deployment = loadDeployment();
69
+ console.log(`@meffecta/agent ${cliVersion()}`);
70
+ console.log(
71
+ `engine ghcr.io/meffecta/agent:${tag}${pinned ? "" : " (not pinned — a checkout, not a release)"}`,
72
+ );
73
+ if (deployment.PROJECT) {
74
+ console.log(`deployment ${deployment.SERVICE} in ${deployment.PROJECT} (${deployment.REGION})`);
75
+ }
76
+ }
77
+
78
+ const [name, ...args] = process.argv.slice(2);
79
+
80
+ if (!name || name === "help" || name === "--help" || name === "-h") {
81
+ help();
82
+ process.exit(0);
83
+ }
84
+ if (name === "steps") {
85
+ steps();
86
+ process.exit(0);
87
+ }
88
+ if (name === "version" || name === "--version" || name === "-v") {
89
+ version();
90
+ process.exit(0);
91
+ }
92
+
93
+ const command = COMMANDS.get(name);
94
+ if (!command) {
95
+ const near = [...COMMANDS.keys()].filter((candidate) => candidate.startsWith(name.slice(0, 3)));
96
+ console.error(`Unknown command: ${name}`);
97
+ if (near.length > 0) {
98
+ console.error(`Did you mean: ${near.join(", ")}?`);
99
+ }
100
+ console.error("Run `meffecta-agent help` for the list.");
101
+ process.exit(2);
102
+ }
103
+
104
+ try {
105
+ // deploy defaults to the engine built alongside this release, so tooling and engine
106
+ // match unless the operator deliberately asks for something else.
107
+ if (name === "deploy" && !args.includes("--tag") && !args.includes("--image")) {
108
+ const { tag, pinned } = engineTag();
109
+ if (pinned) {
110
+ args.push("--tag", tag);
111
+ }
112
+ }
113
+ process.exit((await command.handler(args)) ?? 0);
114
+ } catch (err) {
115
+ if (err instanceof UserError) {
116
+ console.error(err.message);
117
+ process.exit(1);
118
+ }
119
+ throw err;
120
+ }