@rtrentjones/greenlight 0.2.5 → 0.2.6
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 +81 -0
- package/assets/skills/provider-cloudflare/SKILL.md +3 -1
- package/assets/skills/provider-github/SKILL.md +13 -0
- package/assets/skills/provider-oci/SKILL.md +4 -3
- package/dist/bin.js +53 -20
- package/dist/{chunk-KFKYLGFX.js → chunk-LM6M3DIV.js} +4 -0
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @rtrentjones/greenlight
|
|
2
|
+
|
|
3
|
+
The Greenlight CLI — setup and lifecycle for the [Greenlight](https://github.com/RTrentJones/greenlight)
|
|
4
|
+
harness. Greenlight is a **clone-and-own** baseline that turns a domain + API tokens into a live
|
|
5
|
+
personal site plus a self-verifying agentic deploy loop, with plug-and-play subdomain tools (web apps
|
|
6
|
+
or MCP servers). It is provider-agnostic and free-tier-first, and it **edits declarative
|
|
7
|
+
infrastructure-as-code — your CI/CD applies it**. It is not a hosted PaaS.
|
|
8
|
+
|
|
9
|
+
This is the **single published package**: the CLI, with the framework libraries
|
|
10
|
+
(`shared`/`verify`/`adapters`/`loop`) bundled in. The Terraform modules are distributed as git tags
|
|
11
|
+
(pinned in lockstep with this package's version); the skills ship as a Claude Code plugin.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @rtrentjones/greenlight # or npm i / yarn add
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A personal site is a **thin consumer** that depends on this package and owns only its manifest
|
|
20
|
+
(`greenlight.config.ts`) + content. Update with `pnpm update @rtrentjones/greenlight` — no framework
|
|
21
|
+
code to merge.
|
|
22
|
+
|
|
23
|
+
Optional peer features lazy-load and degrade to a failing check if absent (never a crash):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add -D playwright @anthropic-ai/sdk # only for verify modes agent-web / eval
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## CLI
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
greenlight <command>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Command | What it does |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `init --domain <d>` | scaffold the manifest + secrets store |
|
|
38
|
+
| `add <name> --lane <l> --target <t> [--data --auth --envs]` | **infra editor**: manifest entry → emit `infra/<name>.tf` + gather/verify tokens + wire the kit (never applies) |
|
|
39
|
+
| `adopt <name> --repo <url\|path> --lane --target` | onboard an existing tool repo (submodule wrapper, or `--standalone`) |
|
|
40
|
+
| `secrets gather <name> [--repo o/r] [--oci-config <path>]` | guided, link-first token prompts straight to GitHub secrets (no disk, no logs) |
|
|
41
|
+
| `secrets sync [--repo o/r] [--env <env>]` | push `.greenlight/secrets.env` → GitHub Actions secrets |
|
|
42
|
+
| `agent sync` | materialize the agent loop kit (skill + `.mcp.json` + CLAUDE block) into a repo |
|
|
43
|
+
| `preview <name>` | build + serve locally + verify in one command |
|
|
44
|
+
| `verify <name> --env <beta\|prod>` (or `--url`) | run the shared verify harness |
|
|
45
|
+
| `promote <name>` | gated `develop → main` fast-forward (after beta verify) |
|
|
46
|
+
| `deploy <name>` | target deploy hook (e.g. OCI restart = re-pull) |
|
|
47
|
+
| `doctor` / `config` | health checks / load + validate + print the manifest |
|
|
48
|
+
|
|
49
|
+
## The loop
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
greenlight add notes --lane mcp --target oci # one entry → Terraform + tokens + kit
|
|
53
|
+
greenlight verify notes --env beta # api | mcp | playwright | test | agent-web | eval
|
|
54
|
+
greenlight promote notes # gated develop → main, after beta verify passes
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The `verify` gate is the same code CI **and** the agent run — so changes ship with objective
|
|
58
|
+
confidence, not vibes.
|
|
59
|
+
|
|
60
|
+
## Programmatic API
|
|
61
|
+
|
|
62
|
+
Typed helpers for your `greenlight.config.ts` and `verify.config.ts`:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { defineConfig, defineVerify } from '@rtrentjones/greenlight';
|
|
66
|
+
|
|
67
|
+
export default defineConfig({
|
|
68
|
+
domain: 'you.dev',
|
|
69
|
+
tools: { notes: { lane: 'mcp', target: 'oci', data: 'none', auth: 'bearer' } },
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Also exported: `loadConfig`, and the `GreenlightConfig` / `VerifySpec` types.
|
|
74
|
+
|
|
75
|
+
## Links
|
|
76
|
+
|
|
77
|
+
- **Repo + full docs:** <https://github.com/RTrentJones/greenlight>
|
|
78
|
+
- **Architecture:** [docs/architecture.md](https://github.com/RTrentJones/greenlight/blob/main/docs/architecture.md)
|
|
79
|
+
- **Spec:** [greenlight-v1.md](https://github.com/RTrentJones/greenlight/blob/main/greenlight-v1.md)
|
|
80
|
+
|
|
81
|
+
MIT
|
|
@@ -12,10 +12,12 @@ blog and throwaway MCP dev targets.
|
|
|
12
12
|
|
|
13
13
|
## Token — `CLOUDFLARE_API_TOKEN`
|
|
14
14
|
|
|
15
|
-
One token,
|
|
15
|
+
One token, these scopes (a missing scope took down a live apply more than once):
|
|
16
16
|
- **Account · Workers Scripts · Edit** — deploy the keepalive worker / workers-target tools.
|
|
17
17
|
- **Zone · DNS · Edit** — the subdomain CNAMEs.
|
|
18
18
|
- **Account · Account Settings · Read** — read account id.
|
|
19
|
+
- **Account · Cloudflare Tunnel · Edit** — only if a tool uses `target: oci` (the cloudflared
|
|
20
|
+
tunnel). Without it, the tunnel resource fails with **403 Forbidden** on `cfd_tunnel` at apply.
|
|
19
21
|
|
|
20
22
|
Create at dash → My Profile → API Tokens → Custom Token. Store in `.greenlight/secrets.env`
|
|
21
23
|
(gitignored) and push to GitHub Actions with `greenlight secrets sync`. `greenlight add`
|
|
@@ -18,6 +18,19 @@ workflows. The `develop → main` flow is standardized (PR → preview, `develop
|
|
|
18
18
|
use a fine-grained **PAT** with the minimal scopes (e.g. `Secrets: write`, `Administration`
|
|
19
19
|
for protection). Prefer **GitHub OIDC → cloud** over long-lived cloud tokens where supported.
|
|
20
20
|
|
|
21
|
+
### Poly-repo deploy loop — the two option-B PATs
|
|
22
|
+
|
|
23
|
+
An adopted tool (submodule) and its wrapper hand off via two fine-grained PATs (`secrets gather`
|
|
24
|
+
pushes each to the right repo; see docs/provider-tokens.md):
|
|
25
|
+
|
|
26
|
+
- **`GREENLIGHT_DISPATCH_TOKEN`** — on the **tool** repo, scoped **Contents: write** on the
|
|
27
|
+
**wrapper** → the tool's build fires `repository_dispatch` so the wrapper deploys.
|
|
28
|
+
- **`GREENLIGHT_STATUS_TOKEN`** — on the **wrapper** repo, scoped **Commit statuses: write** on the
|
|
29
|
+
**tool** → the wrapper posts deploy/verify status back to the tool's commit.
|
|
30
|
+
|
|
31
|
+
Provider creds (OCI/Cloudflare/…) live **only in the wrapper**; the tool repo holds just the
|
|
32
|
+
dispatch PAT (its build pushes to GHCR with the built-in `github.token`).
|
|
33
|
+
|
|
21
34
|
## Secrets sync
|
|
22
35
|
|
|
23
36
|
`greenlight secrets sync [--repo o/r] [--env <env>]` pushes `.greenlight/secrets.env` to the
|
|
@@ -37,9 +37,10 @@ The `oci` provider (auth below) is added to `infra/main.tf`.
|
|
|
37
37
|
`greenlight secrets gather <tool> --repo <o/r>` pushes the OCI creds straight to GitHub secrets
|
|
38
38
|
(hidden prompts, no disk/logs). **The only manual OCI inputs are the API-key auth values** —
|
|
39
39
|
`TF_VAR_OCI_TENANCY_OCID`, `TF_VAR_OCI_USER_OCID`, `TF_VAR_OCI_FINGERPRINT`, `TF_VAR_OCI_PRIVATE_KEY`
|
|
40
|
-
(PEM), `TF_VAR_OCI_REGION
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
(PEM), `TF_VAR_OCI_REGION`. `TF_VAR_OCI_COMPARTMENT_ID` is **optional** (blank → the tenancy/root
|
|
41
|
+
compartment). Auth is API-key request signing — no bearer, so no fetch-verify. The container
|
|
42
|
+
instance OCID is **not** a manual input — the deploy workflow resolves it at deploy time from OCI
|
|
43
|
+
by the instance's display name (= the tool name), so it's abstracted from the developer.
|
|
43
44
|
|
|
44
45
|
**The VCN, subnet, and availability domain are NOT manual** — they're Terraform: the `oci-network`
|
|
45
46
|
module creates the VCN + a public (egress-only) subnet, and the container-instance module looks the
|
package/dist/bin.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
resolveUrl,
|
|
7
7
|
verifyAll
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LM6M3DIV.js";
|
|
9
9
|
import "./chunk-XBDQJVAX.js";
|
|
10
10
|
import "./chunk-WFZTRXBF.js";
|
|
11
11
|
import "./chunk-KP3Y6WRU.js";
|
|
@@ -92,7 +92,8 @@ function addTool(config, t) {
|
|
|
92
92
|
envs: t.envs ?? ["beta", "prod"],
|
|
93
93
|
...t.dir !== void 0 ? { dir: t.dir } : {},
|
|
94
94
|
...t.adopted ? { adopted: true } : {},
|
|
95
|
-
...t.external ? { external: true } : {}
|
|
95
|
+
...t.external ? { external: true } : {},
|
|
96
|
+
...t.port !== void 0 ? { port: t.port } : {}
|
|
96
97
|
}
|
|
97
98
|
]
|
|
98
99
|
};
|
|
@@ -185,16 +186,17 @@ var PACKS = [
|
|
|
185
186
|
always: true,
|
|
186
187
|
// the zone/DNS provider + Workers (keepalive) for every Greenlight setup
|
|
187
188
|
appliesTo: () => true,
|
|
188
|
-
guide: "docs/provider-tokens.md \u2014 CLOUDFLARE_API_TOKEN (Workers Scripts:Edit + Zone DNS:Edit)",
|
|
189
|
+
guide: "docs/provider-tokens.md \u2014 CLOUDFLARE_API_TOKEN (Workers Scripts:Edit + Zone DNS:Edit + Cloudflare Tunnel:Edit for oci tools)",
|
|
189
190
|
setupUrl: "https://dash.cloudflare.com/profile/api-tokens",
|
|
190
191
|
tokens: [
|
|
191
192
|
{
|
|
192
193
|
envVar: "CLOUDFLARE_API_TOKEN",
|
|
193
|
-
label: "API token \u2014 Workers Scripts:Edit + Zone DNS:Edit",
|
|
194
|
+
label: "API token \u2014 Workers Scripts:Edit + Zone DNS:Edit (+ Cloudflare Tunnel:Edit for oci)",
|
|
194
195
|
scopes: [
|
|
195
196
|
"Account \xB7 Workers Scripts \xB7 Edit",
|
|
196
197
|
"Zone \xB7 DNS \xB7 Edit",
|
|
197
|
-
"Account \xB7 Account Settings \xB7 Read"
|
|
198
|
+
"Account \xB7 Account Settings \xB7 Read",
|
|
199
|
+
"Account \xB7 Cloudflare Tunnel \xB7 Edit (only if a tool uses target: oci)"
|
|
198
200
|
],
|
|
199
201
|
verify: async (t) => {
|
|
200
202
|
const r = await fetch("https://api.cloudflare.com/client/v4/user/tokens/verify", {
|
|
@@ -385,7 +387,7 @@ function tokensForTool(tool) {
|
|
|
385
387
|
}
|
|
386
388
|
|
|
387
389
|
// src/version.ts
|
|
388
|
-
var MODULE_REF = "v0.2.
|
|
390
|
+
var MODULE_REF = "v0.2.6";
|
|
389
391
|
var MODULE_SOURCE_BASE = "git::https://github.com/RTrentJones/greenlight.git//infra/modules";
|
|
390
392
|
function moduleSource(module, ref = MODULE_REF) {
|
|
391
393
|
return `${MODULE_SOURCE_BASE}/${module}?ref=${ref}`;
|
|
@@ -395,6 +397,7 @@ function moduleSource(module, ref = MODULE_REF) {
|
|
|
395
397
|
var hcl = (s) => s.replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
396
398
|
function emitToolTf(opts) {
|
|
397
399
|
const { name, domain, lane, target, data, envs, ref = MODULE_REF } = opts;
|
|
400
|
+
const port = opts.port ?? 8e3;
|
|
398
401
|
const slug = opts.slug ?? `OWNER/${name}`;
|
|
399
402
|
const useSupabase = data === "supabase";
|
|
400
403
|
const useVercel = target === "vercel";
|
|
@@ -467,7 +470,7 @@ variable "${name}_vercel_project_id" {
|
|
|
467
470
|
}
|
|
468
471
|
if (useOci) {
|
|
469
472
|
blocks.push(`# OCI Container Instance (Always-Free Ampere A1) running the tool's GHCR image + a cloudflared
|
|
470
|
-
# sidecar; the tunnel routes ${name}.${domain} \u2192 the container at localhost
|
|
473
|
+
# sidecar; the tunnel routes ${name}.${domain} \u2192 the container at localhost:${port}. The tool's OWN
|
|
471
474
|
# CI builds + pushes the image (provider-agnostic); deploy = restart the instance (re-pull).
|
|
472
475
|
# beta would be a second instance + tunnel route \u2014 mind the free 2-OCPU / 12-GB A1 cap.
|
|
473
476
|
module "${name}_tunnel" {
|
|
@@ -476,7 +479,7 @@ module "${name}_tunnel" {
|
|
|
476
479
|
account_id = var.cloudflare_account_id
|
|
477
480
|
name = "${name}-tunnel"
|
|
478
481
|
ingress = [
|
|
479
|
-
{ hostname = "${name}.${domain}", service = "http://localhost
|
|
482
|
+
{ hostname = "${name}.${domain}", service = "http://localhost:${port}" },
|
|
480
483
|
]
|
|
481
484
|
}
|
|
482
485
|
|
|
@@ -560,11 +563,13 @@ function emitWrapperMainTf(opts) {
|
|
|
560
563
|
if (need.has("supabase")) providerBlocks.push('provider "supabase" {}');
|
|
561
564
|
if (need.has("oci")) {
|
|
562
565
|
providerBlocks.push(`provider "oci" {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
+
# trimspace guards against a trailing newline/space in a pasted secret (a malformed region
|
|
567
|
+
# makes the identity endpoint hostname fail to resolve \u2014 "no such host" \u2014 at plan time).
|
|
568
|
+
tenancy_ocid = trimspace(var.oci_tenancy_ocid)
|
|
569
|
+
user_ocid = trimspace(var.oci_user_ocid)
|
|
570
|
+
fingerprint = trimspace(var.oci_fingerprint)
|
|
566
571
|
private_key = var.oci_private_key
|
|
567
|
-
region = var.oci_region
|
|
572
|
+
region = trimspace(var.oci_region)
|
|
568
573
|
}`);
|
|
569
574
|
}
|
|
570
575
|
const vars = ['variable "cloudflare_zone_id" { type = string }'];
|
|
@@ -1039,7 +1044,7 @@ async function addCommand(args) {
|
|
|
1039
1044
|
const name = args[0];
|
|
1040
1045
|
if (!name || name.startsWith("-")) {
|
|
1041
1046
|
throw new Error(
|
|
1042
|
-
"usage: greenlight add <name> --lane <lane> --target <target> [--data <d>] [--auth <a>] [--envs beta,prod]"
|
|
1047
|
+
"usage: greenlight add <name> --lane <lane> --target <target> [--data <d>] [--auth <a>] [--envs beta,prod] [--port 8000]"
|
|
1043
1048
|
);
|
|
1044
1049
|
}
|
|
1045
1050
|
const lane = flag2(args, "--lane");
|
|
@@ -1049,13 +1054,15 @@ async function addCommand(args) {
|
|
|
1049
1054
|
if (path.endsWith(".example.ts")) {
|
|
1050
1055
|
throw new Error("no greenlight.config.ts \u2014 run `greenlight init` first");
|
|
1051
1056
|
}
|
|
1057
|
+
const portFlag = flag2(args, "--port");
|
|
1052
1058
|
const next = addTool(config, {
|
|
1053
1059
|
name,
|
|
1054
1060
|
lane,
|
|
1055
1061
|
target,
|
|
1056
1062
|
data: flag2(args, "--data"),
|
|
1057
1063
|
auth: flag2(args, "--auth"),
|
|
1058
|
-
envs: flag2(args, "--envs")?.split(",")
|
|
1064
|
+
envs: flag2(args, "--envs")?.split(","),
|
|
1065
|
+
port: portFlag ? Number(portFlag) : void 0
|
|
1059
1066
|
});
|
|
1060
1067
|
const entry = next.tools.find((t) => t.name === name);
|
|
1061
1068
|
const data = entry?.data ?? "none";
|
|
@@ -1094,7 +1101,10 @@ async function addCommand(args) {
|
|
|
1094
1101
|
if (existsSync6(toolTf)) {
|
|
1095
1102
|
console.log(`\xB7 infra/${name}.tf exists \u2014 left as-is`);
|
|
1096
1103
|
} else {
|
|
1097
|
-
writeFileSync3(
|
|
1104
|
+
writeFileSync3(
|
|
1105
|
+
toolTf,
|
|
1106
|
+
emitToolTf({ name, domain: config.domain, lane, target, data, envs, port: entry?.port })
|
|
1107
|
+
);
|
|
1098
1108
|
console.log(`\u2714 wrote infra/${name}.tf (modules: ${providers.join(", ")})`);
|
|
1099
1109
|
}
|
|
1100
1110
|
if (!args.includes("--no-tokens")) {
|
|
@@ -1356,7 +1366,7 @@ jobs:
|
|
|
1356
1366
|
- uses: jdx/mise-action@v2
|
|
1357
1367
|
- run: pnpm install --frozen-lockfile
|
|
1358
1368
|
- run: pip install --quiet oci-cli
|
|
1359
|
-
- name: Deploy (
|
|
1369
|
+
- name: Deploy (resolve instance OCID by name -> restart -> re-pull GHCR image)
|
|
1360
1370
|
env:
|
|
1361
1371
|
# The OCI CLI reuses the SAME TF_VAR_OCI_* secrets the apply uses \u2014 one secret set.
|
|
1362
1372
|
OCI_CLI_TENANCY: \${{ secrets.TF_VAR_OCI_TENANCY_OCID }}
|
|
@@ -1364,8 +1374,31 @@ jobs:
|
|
|
1364
1374
|
OCI_CLI_FINGERPRINT: \${{ secrets.TF_VAR_OCI_FINGERPRINT }}
|
|
1365
1375
|
OCI_CLI_KEY_CONTENT: \${{ secrets.TF_VAR_OCI_PRIVATE_KEY }}
|
|
1366
1376
|
OCI_CLI_REGION: \${{ secrets.TF_VAR_OCI_REGION }}
|
|
1367
|
-
|
|
1368
|
-
run:
|
|
1377
|
+
OCI_COMPARTMENT_ID: \${{ secrets.TF_VAR_OCI_COMPARTMENT_ID }}
|
|
1378
|
+
run: |
|
|
1379
|
+
# The instance OCID is abstracted from the developer: resolve it from OCI by the
|
|
1380
|
+
# instance's display name (= the tool name, set by the oci-container-instance module).
|
|
1381
|
+
# No manually-fetched/stored OCID secret. Compartment falls back to the tenancy (root).
|
|
1382
|
+
set -o pipefail
|
|
1383
|
+
COMPARTMENT="\${OCI_COMPARTMENT_ID:-$OCI_CLI_TENANCY}"
|
|
1384
|
+
echo "Resolving '${name}' (region=$OCI_CLI_REGION)\u2026"
|
|
1385
|
+
oci container-instances container-instance list \\
|
|
1386
|
+
--compartment-id "$COMPARTMENT" --display-name ${name} --all > list.json \\
|
|
1387
|
+
|| { echo "::error::oci list failed (auth/region/compartment) \u2014 see output above"; exit 1; }
|
|
1388
|
+
# OCIDs are identifiers, not secrets (tenancy/compartment are masked by Actions).
|
|
1389
|
+
echo "Matches:"; jq -r '(.data.items // .data // [])[]? | " \\(.["lifecycle-state"] // "?") \\(.id)"' list.json
|
|
1390
|
+
OCID=$(jq -r '[(.data.items // .data // [])[]? | select((.["lifecycle-state"] // "")=="ACTIVE") | .id][0] // ""' list.json)
|
|
1391
|
+
if [ -z "$OCID" ]; then
|
|
1392
|
+
echo "::error::no ACTIVE container instance named '${name}' in compartment $COMPARTMENT"
|
|
1393
|
+
exit 1
|
|
1394
|
+
fi
|
|
1395
|
+
echo "Resolved ${name} instance: $OCID"
|
|
1396
|
+
OCI_CONTAINER_INSTANCE_OCID="$OCID" pnpm exec greenlight deploy ${name} --env prod
|
|
1397
|
+
- name: Verify prod (gate the signal on real health, not just the restart)
|
|
1398
|
+
# The deploy "succeeds" only if the NEW image is actually serving. verify has a built-in
|
|
1399
|
+
# readiness wait (re-pull + container start). A failure here fails the job \u2192 the status
|
|
1400
|
+
# posted back is red. oci is verify-gated direct-to-prod (no cheap standing beta on free A1).
|
|
1401
|
+
run: pnpm exec greenlight verify ${name} --env prod
|
|
1369
1402
|
- name: Report status back to ${toolRepo}
|
|
1370
1403
|
if: \${{ always() && github.event.client_payload.sha != '' }}
|
|
1371
1404
|
env:
|
|
@@ -1488,7 +1521,7 @@ Next:
|
|
|
1488
1521
|
git commit && git push # CI (infra.yml) applies. Tool's CI builds; wrapper deploys.${target === "oci" ? `
|
|
1489
1522
|
Secrets (guided): greenlight secrets gather ${name} --repo <wrapper> # TF_VAR_OCI_* + GREENLIGHT_STATUS_TOKEN
|
|
1490
1523
|
greenlight secrets gather ${name} --repo ${slug} # GREENLIGHT_DISPATCH_TOKEN
|
|
1491
|
-
|
|
1524
|
+
The instance OCID is auto-resolved by the deploy workflow (by display name) \u2014 nothing to set.` : ""}`);
|
|
1492
1525
|
}
|
|
1493
1526
|
async function adoptStandalone(ctx) {
|
|
1494
1527
|
const { name, repoArg, lane, target, data, auth, envs, domain, reg, regPath } = ctx;
|
|
@@ -1710,7 +1743,7 @@ async function deployCommand(args) {
|
|
|
1710
1743
|
}
|
|
1711
1744
|
const { config } = await loadManifest();
|
|
1712
1745
|
const entry = resolveEntry(config, name);
|
|
1713
|
-
if (entry.external) {
|
|
1746
|
+
if (entry.external && entry.target !== "oci") {
|
|
1714
1747
|
throw new Error(`"${name}" is external (registry pointer) \u2014 deploy it from its own repo`);
|
|
1715
1748
|
}
|
|
1716
1749
|
const adapter = createAdapter(entry.target, { domain: config.domain, name: entry.name });
|
|
@@ -33,6 +33,10 @@ var ToolSchema = z.object({
|
|
|
33
33
|
access: AccessEnum.default("public"),
|
|
34
34
|
envs: z.array(EnvEnum).nonempty("a tool needs at least one env"),
|
|
35
35
|
adopted: z.boolean().default(false),
|
|
36
|
+
// The port the container listens on (target: oci). The tunnel routes to localhost:<port>;
|
|
37
|
+
// defaults to 8000 (the mcp/FastMCP convention). Set it for a lane:docker tool on a different
|
|
38
|
+
// port so the oci modules stay generic. Ignored by non-oci targets.
|
|
39
|
+
port: z.number().int().positive().optional(),
|
|
36
40
|
// Directory the tool builds/deploys from. Defaults to tools/<name>; a standalone
|
|
37
41
|
// (poly-repo) tool sets '.' (the repo root).
|
|
38
42
|
dir: z.string().optional(),
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rtrentjones/greenlight",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Greenlight CLI — setup and lifecycle for the harness.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@rtrentjones/greenlight-adapters": "0.2.4",
|
|
35
|
+
"@rtrentjones/greenlight-loop": "0.2.4",
|
|
35
36
|
"@rtrentjones/greenlight-shared": "0.2.4",
|
|
36
|
-
"@rtrentjones/greenlight-verify": "0.2.4"
|
|
37
|
-
"@rtrentjones/greenlight-loop": "0.2.4"
|
|
37
|
+
"@rtrentjones/greenlight-verify": "0.2.4"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "node scripts/copy-assets.mjs && tsup",
|