@meffecta/agent 1.0.10 → 1.0.11

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.
package/README.md CHANGED
@@ -92,7 +92,6 @@ not consent. Secret values are never printed, by any command.
92
92
  ## Operating it
93
93
 
94
94
  ```bash
95
- npx @meffecta/agent steps # the set-up, in order
96
95
  npx @meffecta/agent help # every command — each also takes --help
97
96
  npx @meffecta/agent doctor # the one to run when something feels wrong
98
97
  ```
@@ -18,40 +18,14 @@ import { installMethod } from "../lib/version.js";
18
18
 
19
19
  // Tools first: every command below needs gcloud, and the very first one already does. The
20
20
  // claude CLI is the odd one out — it is used once, to mint the token, and never again.
21
- const STEPS = [
22
- [
23
- "",
24
- "Install gcloud, docker, node 24+ and git. Also the claude CLI — it is\n used once, for the token two steps down, and not after that.",
25
- ],
26
- [
27
- "create-project",
28
- "A new GCP project. This and the next step are per GCP account, not\n per deployment — skip both if you are adding a second agent to a project you\n already have.",
29
- ],
30
- ["link-billing", "Attach a billing account, or nothing can run."],
31
- [
32
- "mint-gmail",
33
- "Credentials: `claude setup-token`, a GitHub token that can clone your repos,\n and — only if this agent reads or sends mail — one token per mailbox.",
34
- ],
35
- [
36
- "init",
37
- "Create your content repo — jobs/, SYSTEM.md, systems/, and a deployment.env in it.\n Run everything from there; it is what says which deployment you mean.",
38
- ],
39
- ["setup-infra", "Provision GCP, the run queue, and the Cloud Run service shell.\n --dry-run shows it first."],
40
- ["set-secret", "AGENT_API_SECRET --random, CLAUDE_CODE_OAUTH_TOKEN, GITHUB_TOKEN."],
41
- ["set-env", "GIT_REPO_URL=… — the service will not boot without it."],
42
- [
43
- "deploy",
44
- "Roll out the engine image, then create its triggers — scaled to zero it fires\n nothing without them, and only the running engine knows which jobs exist.",
45
- ],
46
- ["doctor", 'Check it. Then: jobs, run <job>, ask "…".'],
47
- ];
48
21
 
49
22
  function help() {
50
23
  const { tag, pinned } = engineTag();
51
24
  console.log("meffecta-agent — set up and operate a Meffecta Agent deployment\n");
52
25
  console.log("Usage: meffecta-agent <command> [options]");
53
26
  console.log(" npx @meffecta/agent <command> [options]\n");
54
- console.log("Run it from your content repo — the directory holding deployment.env and jobs/.\n");
27
+ console.log("Run it from your content repo — the directory holding deployment.env and jobs/.");
28
+ console.log("Setting one up for the first time: https://agent.meffecta.com\n");
55
29
  for (const group of GROUPS) {
56
30
  console.log(`${group.title}:`);
57
31
  const width = Math.max(...group.commands.map(([name]) => name.length));
@@ -63,7 +37,6 @@ function help() {
63
37
  }
64
38
  console.log("");
65
39
  }
66
- console.log(" steps The whole set-up, in order, with the command for each");
67
40
  console.log(" version This CLI and the engine build it deploys");
68
41
  console.log(" upgrade Whether a newer one is out, and what to run\n");
69
42
  console.log("Every command takes --help, and every option it documents — so");
@@ -71,20 +44,6 @@ function help() {
71
44
  console.log(`Engine: ghcr.io/meffecta/agent:${tag}${pinned ? " (pinned to this release)" : ""}`);
72
45
  }
73
46
 
74
- function steps() {
75
- console.log("Setting up a deployment, in order.\n");
76
- STEPS.forEach(([command, note], index) => {
77
- const label = `${String(index + 1).padStart(2)}.`;
78
- console.log(`${label} ${note}`);
79
- if (command) {
80
- console.log(` meffecta-agent ${command}`);
81
- }
82
- console.log("");
83
- });
84
- console.log("Each step is checkable: `meffecta-agent doctor` after the last one should come");
85
- console.log("back clean, and `meffecta-agent jobs` should list your jobs.");
86
- }
87
-
88
47
  function version() {
89
48
  const { tag, pinned } = engineTag();
90
49
  const deployment = loadDeployment();
@@ -104,7 +63,7 @@ const { analytics, firstRun } = openAnalytics({ version: cliVersion() });
104
63
  // The name is matched against the known commands before it is ever recorded. Whatever an
105
64
  // operator actually typed is theirs — a mistyped command could be anything, including
106
65
  // something they would not want leaving the machine.
107
- const BUILT_IN = new Set(["help", "steps", "version"]);
66
+ const BUILT_IN = new Set(["help", "version"]);
108
67
  function recorded(candidate) {
109
68
  if (!candidate) {
110
69
  return "help";
@@ -146,10 +105,6 @@ if (!name || name === "help" || name === "--help" || name === "-h") {
146
105
  help();
147
106
  await finish(0, "ok");
148
107
  }
149
- if (name === "steps") {
150
- steps();
151
- await finish(0, "ok");
152
- }
153
108
  if (name === "version" || name === "--version" || name === "-v") {
154
109
  version();
155
110
  await finish(0, "ok");
@@ -20,20 +20,12 @@ PROJECT=acme-agent-123456
20
20
  # The Cloud Run service.
21
21
  SERVICE=acme-agent
22
22
 
23
- # Docker repo in that project, holding images mirrored from GHCR.
23
+ # A repository in that project standing in front of ghcr.io. Cloud Run can only pull from
24
+ # Artifact Registry, so this is how it reaches the engine image: AR fetches the public
25
+ # image itself, on demand, and caches it. Nothing is copied by hand and no docker is
26
+ # needed on your machine at any point.
24
27
  ARTIFACT_REPO=acme-agent-images
25
28
 
26
- # How the engine image reaches Cloud Run, which can only pull from Artifact Registry.
27
- # proxy (default) ARTIFACT_REPO is a remote repository in front of ghcr.io. AR fetches
28
- # the public engine image on demand — nothing is copied, and no docker is needed
29
- # on your machine at any point.
30
- # mirror the older path: pull from ghcr.io here, retag, push. Needs docker running and
31
- # `gcloud auth configure-docker <region>-docker.pkg.dev`, and pushes several GB
32
- # per engine version. Use it only if your policy forbids AR reaching the internet.
33
- # A repository's mode is fixed when it is created, so changing this means a new
34
- # ARTIFACT_REPO name and another setup-infrastructure run.
35
- REGISTRY_MODE=proxy
36
-
37
29
  REGION=europe-west1
38
30
 
39
31
  # Optional. Unset = whichever account gcloud is logged in as. Set it when you work across
@@ -43,9 +35,6 @@ REGION=europe-west1
43
35
  # Optional. The service's TIMEZONE env var, which is what cron schedules are read in.
44
36
  # TIMEZONE=Europe/Stockholm
45
37
 
46
- # Optional. Where published engine images come from.
47
- # GHCR_IMAGE=ghcr.io/meffecta/agent
48
-
49
38
  # Optional. The runtime shape asserted on every rollout, so the deployed service
50
39
  # matches this file rather than whatever was last changed by hand. The defaults below are
51
40
  # what `meffecta-agent setup-infra` provisions, so leaving them out is the same as setting
package/engine.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "engineTag": "1.0.10",
3
- "builtFrom": "36d54c192cfa454f2e4485414ac854b3be86c8f7"
2
+ "engineTag": "1.0.11",
3
+ "builtFrom": "c0f07973f25a4d8872a3e4dd28c44cd94f2e87a7"
4
4
  }
package/lib/commands.js CHANGED
@@ -52,8 +52,8 @@ export function engineTag() {
52
52
  const { engineTag: tag, builtFrom } = JSON.parse(readFileSync(file, "utf8"));
53
53
  // Only a release is pinned. `npm pack` run by hand writes this file into the working
54
54
  // tree with builtFrom "local", where it would otherwise survive and pin `deploy` to a
55
- // version that was never published the image is not there and the mirror fails with
56
- // nothing to say why that tag was chosen.
55
+ // version that was never published, where the pull fails with nothing to say why that
56
+ // tag was chosen.
57
57
  if (!tag || builtFrom === "local" || existsSync(resolve(packageRoot, "..", "src", "index.ts"))) {
58
58
  return { tag: "latest", pinned: false };
59
59
  }
@@ -584,13 +584,65 @@ Set the values with \`meffecta-agent set-secret <NAME>\` (credentials) or
584
584
  systems the engine already has skills for and walks through each.
585
585
  `;
586
586
 
587
+ /**
588
+ * The deployment prompt, put in front of every job's own prompt. A starter rather than an
589
+ * empty file, because the engine's baseline already states how a run works, what a run may
590
+ * act on and what it may never say — so what is left is the half only this organisation can
591
+ * write, and a blank file gives no hint which half that is.
592
+ */
593
+ const SYSTEM_EXAMPLE = `# Who you work for
594
+
595
+ Replace all of this. It goes in front of every job's prompt, so it is for what holds across
596
+ all of them and only that — rules, not facts. What the agent should KNOW about a business
597
+ belongs in \`worlds/\`.
598
+
599
+ You work for **Acme AB**. You report to **Alex Doe, alex@acme.com**, and your output is for
600
+ Alex unless the job you are running names someone else.
601
+
602
+ ## How to write
603
+
604
+ - Lead with what you found. No preamble, and do not restate the task.
605
+ - Short. This gets read between meetings.
606
+ - One thing you are unsure of, said plainly, is worth more than three you are not.
607
+
608
+ ## People
609
+
610
+ - Alex Doe, alex@acme.com — runs the company. Everything comes here by default.
611
+ `;
612
+
613
+ /**
614
+ * A first job that proves the whole path — clone, system prompt, register, memory — and
615
+ * reaches nothing real while doing it. Named to match \`systems/example.md\`: both are the
616
+ * shape of the thing, shown once, and deleted when a real one exists.
617
+ *
618
+ * No \`cron:\`, deliberately. A scaffolded job that started firing on a schedule the moment
619
+ * the service booted would be the wrong first impression of what these files do.
620
+ */
621
+ const JOB_EXAMPLE = `---
622
+ model: sonnet
623
+ ---
624
+
625
+ A first run, to prove the plumbing. It reaches no real system and changes nothing.
626
+
627
+ Report, in under ten lines:
628
+
629
+ 1. Who you work for — one line, from \`SYSTEM.md\`.
630
+ 2. What you can reach — every file in \`systems/\`, and for each one whether the variables it
631
+ requires are set. **Names and set/unset only, never a value.** A system whose variables
632
+ are missing is one you would report as unavailable.
633
+ 3. Where your memory directory is, and whether \`MEMORY.md\` is in it yet.
634
+
635
+ Then add one dated line to your \`MEMORY.md\` saying this ran.
636
+
637
+ When you have a real job, write it in a file of its own and delete this one. The frontmatter
638
+ above the \`---\` is the settings; everything below it is what you would have said to a
639
+ colleague.
640
+ `;
641
+
587
642
  async function init(args) {
588
- const { copyFileSync, existsSync: exists, writeFileSync } = await import("node:fs");
589
- const target = resolve(process.cwd(), "deployment.env");
590
- if (exists(target) && !args.includes("--force")) {
591
- console.log(`deployment.env already exists here. Leaving it alone (--force to replace).`);
592
- return 0;
593
- }
643
+ const { copyFileSync, existsSync: exists, mkdirSync, writeFileSync } = await import("node:fs");
644
+ const force = args.includes("--force");
645
+
594
646
  const template = [
595
647
  resolve(packageRoot, "deployment.env.example"),
596
648
  resolve(packageRoot, "..", "deployment.env.example"),
@@ -598,28 +650,55 @@ async function init(args) {
598
650
  if (!template) {
599
651
  throw new UserError("The deployment.env template is missing from this install — reinstall @meffecta/agent.");
600
652
  }
601
- copyFileSync(template, target);
602
- console.log("✅ Wrote deployment.env here.\n");
603
- console.log("Fill in PROJECT and SERVICE those two are never guessedthen commit it.");
604
- console.log("It belongs in your content repo, beside jobs/, because which GCP project a");
605
- console.log("set of jobs runs as is a fact about the deployment and none of it is secret.\n");
606
-
607
- // The register is read by the agent itself at run time, not just by people: the skills
608
- // are shared across deployments and name no deployment's variables, so without it a run
609
- // knows how a system works and not which variable holds its credential.
610
- const { mkdirSync } = await import("node:fs");
611
- const registerDir = resolve(process.cwd(), "systems");
612
- if (exists(registerDir)) {
613
- console.log("systems/ already exists here. Leaving it alone.\n");
614
- } else {
615
- mkdirSync(registerDir, { recursive: true });
616
- writeFileSync(resolve(registerDir, "example.md"), REGISTER_EXAMPLE);
617
- console.log("✅ Wrote systems/example.md the register of what this agent can reach.\n");
618
- console.log("One file per system, added as you connect each one. The agent reads them at");
619
- console.log("run time to find out which variable holds which credential, so a system with");
620
- console.log("no file here is one it will report as unavailable.\n");
621
- }
622
- console.log("Next: meffecta-agent steps");
653
+
654
+ // Four artifacts, each decided on its own. `init` is run in a repo that already holds
655
+ // some of themafter an upgrade, or because a first attempt stopped halfway and one
656
+ // file being present must not decide the fate of the other three.
657
+ const SCAFFOLD = [
658
+ ["deployment.env", "which GCP project and service this deployment is", (to) => copyFileSync(template, to)],
659
+ [
660
+ "SYSTEM.md",
661
+ "who the agent works for read at the start of every run",
662
+ (to) => writeFileSync(to, SYSTEM_EXAMPLE),
663
+ ],
664
+ [
665
+ "systems/example.md",
666
+ "one file per system it may reach, and the variables for each",
667
+ (to) => writeFileSync(to, REGISTER_EXAMPLE),
668
+ ],
669
+ ["jobs/example.md", "one file per job: a few settings, then the prompt", (to) => writeFileSync(to, JOB_EXAMPLE)],
670
+ ];
671
+
672
+ const wrote = [];
673
+ const kept = [];
674
+ for (const [name, purpose, produce] of SCAFFOLD) {
675
+ const to = resolve(process.cwd(), name);
676
+ if (exists(to) && !force) {
677
+ kept.push([name, purpose]);
678
+ continue;
679
+ }
680
+ mkdirSync(dirname(to), { recursive: true });
681
+ produce(to);
682
+ wrote.push([name, purpose]);
683
+ }
684
+
685
+ const pad = Math.max(...SCAFFOLD.map(([name]) => name.length)) + 2;
686
+ for (const [name, purpose] of wrote) {
687
+ console.log(`✅ ${name.padEnd(pad)}${purpose}`);
688
+ }
689
+ for (const [name, purpose] of kept) {
690
+ console.log(`• ${name.padEnd(pad)}${purpose} — already here, left alone`);
691
+ }
692
+ if (kept.length && !force) {
693
+ console.log("\n--force replaces the ones already here.");
694
+ }
695
+
696
+ console.log("\nFill in PROJECT and SERVICE in deployment.env — those two are never guessed —");
697
+ console.log("and say who the agent works for in SYSTEM.md. Both example files are the shape of");
698
+ console.log("the thing: rename them after something real, and delete what is left.");
699
+ console.log("\nThen commit all of it. None of it is secret, and a clone of this repo is");
700
+ console.log("everything an operator needs.");
701
+ console.log("\nThe set-up in order, with the command for each part: https://agent.meffecta.com");
623
702
  return 0;
624
703
  }
625
704
 
@@ -640,7 +719,7 @@ export const GROUPS = [
640
719
  // next group so a first deployment is not a shopping list.
641
720
  title: "Stand one up (once, in order)",
642
721
  commands: [
643
- ["init", "Start a deployment.env here, from the template", init],
722
+ ["init", "Scaffold a content repo here: deployment.env, SYSTEM.md, systems/, jobs/", init],
644
723
  [
645
724
  "setup-infra",
646
725
  "Provision GCP: registry, buckets, service account, run queue, service shell",
@@ -668,7 +747,7 @@ export const GROUPS = [
668
747
  script("mint-gmail-token.mjs"),
669
748
  ],
670
749
  ["mint-graph", "A Microsoft Graph refresh token, per mailbox", script("mint-graph-token.mjs")],
671
- ["artifact-cleanup", "Expire mirrored images in the deployment's registry", script("set-artifact-cleanup.sh")],
750
+ ["artifact-cleanup", "Expire cached images in the deployment's registry", script("set-artifact-cleanup.sh")],
672
751
  ],
673
752
  note: [
674
753
  "A new credential is `set-secret` again, plus `set-env` for whatever names the",
package/lib/doctor.js CHANGED
@@ -439,7 +439,7 @@ export async function doctor() {
439
439
  } else {
440
440
  r.warn(
441
441
  "No registry cleanup policy",
442
- "Mirrored engine images accumulate; they are a cache of GHCR and can be expired freely.",
442
+ "Cached engine images accumulate; they are a copy of GHCR and can be expired freely.",
443
443
  "meffecta-agent artifact-cleanup",
444
444
  );
445
445
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meffecta/agent",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Set up and operate a Meffecta Agent deployment — a self-hosted Claude Code job runner.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/deploy.sh CHANGED
@@ -7,18 +7,10 @@ set -euo pipefail
7
7
  # new engine version is the deployment's call, not the engine repo's. This script is
8
8
  # that call, and it is the same script for the vendor's own instance and for a client's.
9
9
  #
10
- # Cloud Run can only pull from Artifact Registry, never from GHCR directly, so an image
11
- # has to reach the deployment's own AR first. Two ways:
12
- #
13
- # 1. Mirror (default here): pull from GHCR, retag, push to AR. Needs docker locally,
14
- # plus `gcloud auth configure-docker <region>-docker.pkg.dev` once, and — for a
15
- # private GHCR package — `docker login ghcr.io` with a PAT that has read:packages.
16
- # 2. AR remote repository: a one-time pull-through proxy of GHCR in the deployment's
17
- # project, after which no local docker is involved. Then point --image straight at
18
- # the proxied path and skip the mirror:
19
- # gcloud artifacts repositories create ghcr-remote --repository-format=docker \
20
- # --mode=remote-repository --location=<region> \
21
- # --remote-docker-repo=https://ghcr.io # + upstream creds for a private package
10
+ # Cloud Run can only pull from Artifact Registry, never from GHCR directly. ARTIFACT_REPO
11
+ # is a remote repository standing in front of ghcr.io, so a tag resolves to a path inside
12
+ # it and Artifact Registry fetches the public engine image itself, on demand. Nothing is
13
+ # copied by hand and no docker is involved not here, not in CI.
22
14
  #
23
15
  # What it writes, and what it deliberately does not.
24
16
  #
@@ -71,62 +63,14 @@ require_gcloud
71
63
  resolve_deployment
72
64
  announce_target "Deploying ${SERVICE}"
73
65
 
74
- # --image is taken as-is. Otherwise --tag resolves against this deployment's registry, and
75
- # how it gets there depends on REGISTRY_MODE: `proxy` names the image inside a remote
76
- # repository and lets Artifact Registry fetch it on demand; `mirror` pulls and pushes it
77
- # from here, which is the only path that needs docker.
78
- if [ -z "${IMAGE}" ] && [ "${REGISTRY_MODE}" = "proxy" ]; then
79
- # A remote repository preserves the upstream path, so ghcr.io/meffecta/agent is served at
80
- # <repo>/meffecta/agent. Nothing is copied and nothing is pushed: the first pull populates
81
- # AR's cache, and Cloud Run's own pull is what triggers it.
66
+ # --image is taken as-is. Otherwise --tag resolves to a path inside the deployment's own
67
+ # remote repository, which preserves the upstream one: ghcr.io/meffecta/agent is served at
68
+ # <repo>/meffecta/agent. Nothing is copied and nothing is pushed Cloud Run's own pull is
69
+ # what populates the cache.
70
+ if [ -z "${IMAGE}" ]; then
82
71
  IMAGE="${REGION}-docker.pkg.dev/${PROJECT}/${ARTIFACT_REPO}/${GHCR_PATH}:${TAG}"
83
72
  echo "🔗 Through the registry proxy: ${IMAGE}"
84
73
  echo " (Artifact Registry fetches ${GHCR_IMAGE}:${TAG} on demand — no local docker)"
85
- elif [ -z "${IMAGE}" ]; then
86
- SOURCE="${GHCR_IMAGE}:${TAG}"
87
- IMAGE="${REGION}-docker.pkg.dev/${PROJECT}/${ARTIFACT_REPO}/${SERVICE}:${TAG}"
88
- command -v docker >/dev/null || {
89
- echo "docker is required to mirror ${SOURCE} into Artifact Registry." >&2
90
- echo "REGISTRY_MODE=proxy in deployment.env avoids it entirely — Artifact Registry" >&2
91
- echo "fetches from ghcr.io itself. After changing it, re-run:" >&2
92
- echo " $(cmd setup-infra)" >&2
93
- exit 1
94
- }
95
- echo "📥 Mirroring ${SOURCE} → ${IMAGE}"
96
- if [ "${DRY_RUN}" = true ]; then
97
- echo " (dry run: would pull, retag and push)"
98
- else
99
- if ! docker pull "${SOURCE}"; then
100
- cat >&2 <<EOF
101
-
102
- Could not pull ${SOURCE}.
103
-
104
- The published engine image is public, so this needs no login — check the tag exists
105
- and that the host can reach ghcr.io:
106
-
107
- curl -sS "https://ghcr.io/token?scope=repository:meffecta/agent:pull&service=ghcr.io"
108
-
109
- If you are pointing GHCR_IMAGE at a private package of your own instead, that one does
110
- need a token with the read:packages scope, which the gh CLI does not request by default:
111
-
112
- gh auth refresh -h github.com -s read:packages
113
- gh auth token | docker login ghcr.io -u <your-github-username> --password-stdin
114
- EOF
115
- exit 1
116
- fi
117
- docker tag "${SOURCE}" "${IMAGE}"
118
- if ! docker push "${IMAGE}"; then
119
- cat >&2 <<EOF
120
-
121
- Could not push to ${IMAGE}.
122
-
123
- Artifact Registry needs docker configured for this region once:
124
-
125
- gcloud auth configure-docker ${REGION}-docker.pkg.dev
126
- EOF
127
- exit 1
128
- fi
129
- fi
130
74
  fi
131
75
 
132
76
  # scale-to-zero is the default and what sync-triggers.sh's triggers assume: no CPU
@@ -64,13 +64,11 @@ resolve_deployment() {
64
64
  load_deployment_config
65
65
  REGION="${REGION:-europe-west1}"
66
66
  ARTIFACT_REPO="${ARTIFACT_REPO:-${SERVICE:-agent}-images}"
67
+ # Where the published engine images are. An environment override exists for building
68
+ # against a fork of the engine, and is deliberately not a deployment.env setting: a
69
+ # deployment points at the engine, and choosing a different one is not a thing to
70
+ # configure by editing a committed file.
67
71
  GHCR_IMAGE="${GHCR_IMAGE:-ghcr.io/meffecta/agent}"
68
- # How the engine image reaches Cloud Run, which can only ever pull from Artifact
69
- # Registry. `proxy` makes ARTIFACT_REPO a remote repository in front of ghcr.io, so AR
70
- # fetches on demand and no local docker is involved at all. `mirror` is the older path:
71
- # pull from ghcr.io on the operator's machine, retag, push — which needs docker, and
72
- # pushes several gigabytes for every engine version.
73
- REGISTRY_MODE="${REGISTRY_MODE:-proxy}"
74
72
  GHCR_PATH="${GHCR_IMAGE#*/}" # ghcr.io/meffecta/agent → meffecta/agent
75
73
  TIMEZONE="${TIMEZONE:-Europe/Stockholm}"
76
74
 
@@ -4,7 +4,7 @@ set -euo pipefail
4
4
  # Stop a deployment's Artifact Registry filling up with engine images it will never use
5
5
  # again.
6
6
  #
7
- # The images there are a **cache**, not an archive: deploy.sh mirrors them from
7
+ # The images there are a **cache**, not an archive: Artifact Registry fetches them from
8
8
  # ghcr.io/meffecta/agent because Cloud Run cannot pull from GHCR directly. GHCR keeps the
9
9
  # real copies, so anything deleted here comes back on the next `deploy.sh --tag <that one>`.
10
10
  # That is what makes an aggressive policy correct for this repo and wrong for a repo whose
@@ -119,33 +119,26 @@ run ${GC} services enable \
119
119
  analyticsdata.googleapis.com \
120
120
  iam.googleapis.com
121
121
 
122
- echo "🐳 Artifact Registry repo (${REGISTRY_MODE})..."
122
+ echo "🐳 Artifact Registry repo..."
123
123
  if ! ${GC} artifacts repositories describe "${ARTIFACT_REPO}" --location="${REGION}" &>/dev/null; then
124
- if [ "${REGISTRY_MODE}" = "proxy" ]; then
125
- # A remote repository standing in front of ghcr.io. Cloud Run can only pull from
126
- # Artifact Registry, and this is how that requirement is met without a local docker:
127
- # AR fetches the public engine image itself, on demand, and caches it. No upstream
128
- # credentials the engine package is public, which is what makes this the simple path.
129
- run ${GC} artifacts repositories create "${ARTIFACT_REPO}" --location="${REGION}" \
130
- --repository-format=docker --mode=remote-repository \
131
- --remote-docker-repo="https://${GHCR_IMAGE%%/*}" \
132
- --description="Proxy of ${GHCR_IMAGE%%/*} — the engine image is fetched on demand"
133
- else
134
- run ${GC} artifacts repositories create "${ARTIFACT_REPO}" --location="${REGION}" --repository-format=docker
135
- fi
124
+ # A remote repository standing in front of ghcr.io. Cloud Run can only pull from
125
+ # Artifact Registry, and this is how that requirement is met without any docker: AR
126
+ # fetches the public engine image itself, on demand, and caches it. No upstream
127
+ # credentials, because the engine package is public.
128
+ run ${GC} artifacts repositories create "${ARTIFACT_REPO}" --location="${REGION}" \
129
+ --repository-format=docker --mode=remote-repository \
130
+ --remote-docker-repo="https://${GHCR_IMAGE%%/*}" \
131
+ --description="Proxy of ${GHCR_IMAGE%%/*} — the engine image is fetched on demand"
136
132
  else
137
133
  ACTUAL_MODE=$(${GC} artifacts repositories describe "${ARTIFACT_REPO}" --location="${REGION}" --format='value(mode)')
138
- case "${ACTUAL_MODE}:${REGISTRY_MODE}" in
139
- REMOTE_REPOSITORY:proxy|STANDARD_REPOSITORY:mirror) ;;
140
- *)
141
- # A repository's mode is fixed at creation, so this is not something to "fix" here —
142
- # deploy.sh would resolve an image path the repository cannot serve, and the failure
143
- # would arrive as a Cloud Run pull error with nothing pointing back to this.
144
- echo " ${ARTIFACT_REPO} is a ${ACTUAL_MODE} but REGISTRY_MODE=${REGISTRY_MODE}." >&2
145
- echo " A repository's mode cannot be changed. Either set REGISTRY_MODE to match, or" >&2
146
- echo " name a different ARTIFACT_REPO in deployment.env and re-run this." >&2
147
- ;;
148
- esac
134
+ if [ "${ACTUAL_MODE}" != "REMOTE_REPOSITORY" ]; then
135
+ # A repository's mode is fixed at creation, so this is not something to "fix" here —
136
+ # deploy.sh would resolve an image path the repository cannot serve, and the failure
137
+ # would arrive as a Cloud Run pull error with nothing pointing back to this.
138
+ echo "⚠ ${ARTIFACT_REPO} is a ${ACTUAL_MODE}, which cannot serve the engine image." >&2
139
+ echo " A repository's mode cannot be changed. Name a different ARTIFACT_REPO in" >&2
140
+ echo " deployment.env and re-run this." >&2
141
+ fi
149
142
  fi
150
143
  # The images here are a cache of ghcr.io/meffecta/agent, not an archive, so they are worth
151
144
  # expiring — see set-artifact-cleanup.sh for why that is safe and what the policy keeps.
@@ -198,8 +191,8 @@ if ! ${GC} iam service-accounts describe "${DEPLOY_SA}" &>/dev/null; then
198
191
  sleep 5
199
192
  done
200
193
  fi
201
- # What a DEPLOY needs, and nothing a BUILD would: roll out a revision, pull (or in mirror
202
- # mode push) the engine image, and read this project's enabled services.
194
+ # What a DEPLOY needs, and nothing a BUILD would: roll out a revision, pull the engine
195
+ # image, and read this project's enabled services.
203
196
  #
204
197
  # Deliberately NOT granted: secretmanager.secretAccessor. deploy.sh re-syncs the Cloud
205
198
  # Scheduler triggers, and that step reads the API secret — so a CI deploy runs
@@ -334,7 +327,7 @@ if has_cli; then
334
327
  echo ""
335
328
  echo " ${MEFFECTA_CLI} connect # lists them; connect <name> walks through one"
336
329
  echo ""
337
- echo " ${MEFFECTA_CLI} steps the whole set-up, in order"
330
+ echo " The whole set-up, in order: https://agent.meffecta.com"
338
331
  echo " ${MEFFECTA_CLI} doctor what is still missing, once something is running"
339
332
  fi
340
333
  if [ "${SETUP_CI_DEPLOY}" = "true" ]; then
@@ -6,8 +6,8 @@ set -euo pipefail
6
6
  # The engine's source repo is private; the image is not. The operator tooling — these
7
7
  # scripts and the config template — ships inside ghcr.io/meffecta/agent, so a deployment run
8
8
  # by someone outside Meffecta gets its tooling from the same public artifact it gets its
9
- # engine from, and never needs the source. What a deployment is documented by is the CLI's
10
- # own help: `meffecta-agent help` and `meffecta-agent steps`.
9
+ # engine from, and never needs the source. A deployment is documented by
10
+ # https://agent.meffecta.com and by the CLI's own `meffecta-agent help`.
11
11
  #
12
12
  # The tooling belongs in your content repo, committed next to jobs/ and deployment.env.
13
13
  # That is what makes a fresh clone of that repo enough to operate the deployment, and it
@@ -26,9 +26,8 @@ set -euo pipefail
26
26
  # scripts/update-tooling.sh --tag sha-558a144 # match a specific one, e.g. what you run
27
27
  # scripts/update-tooling.sh --dry-run # show what would change, touch nothing
28
28
  #
29
- # Needs docker, which the default deploy path needs anyway (it mirrors the image into your
30
- # Artifact Registry). A deployment using an AR remote repository instead still needs it
31
- # here — once per engine version, rather than once per deploy.
29
+ # Needs docker, and is the only thing here that does: the files are lifted out of the
30
+ # published image with `docker cp`. Once per engine version, and never during a deploy.
32
31
 
33
32
  . "$(dirname "${BASH_SOURCE[0]}")/lib/cli-names.sh"
34
33