@open-everywhere/create 0.1.4
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/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +69 -0
- package/bin/create-open-everywhere.js +57 -0
- package/package.json +57 -0
- package/src/cli.ts +241 -0
- package/src/deploy-probe.ts +99 -0
- package/src/engine.ts +106 -0
- package/src/env-file.ts +53 -0
- package/src/scaffold.ts +188 -0
- package/src/steps/build-deploy.ts +78 -0
- package/src/steps/database.ts +85 -0
- package/src/steps/env-checklist.ts +55 -0
- package/src/steps/index.ts +49 -0
- package/src/steps/prerequisites.ts +50 -0
- package/src/steps/repo.ts +69 -0
- package/src/steps/roster.ts +111 -0
- package/src/steps/slack.ts +74 -0
- package/src/steps/vercel.ts +40 -0
- package/src/steps/verify.ts +119 -0
- package/src/version.ts +20 -0
- package/template/.env.example +58 -0
- package/template/.mcp.json +8 -0
- package/template/AGENTS.md +62 -0
- package/template/CLAUDE.md +1 -0
- package/template/README.md +87 -0
- package/template/agent/access/access-requests.ts +69 -0
- package/template/agent/access/browser-hosts.ts +28 -0
- package/template/agent/access/bundles.ts +78 -0
- package/template/agent/access/connector-credentials.ts +74 -0
- package/template/agent/access/credential-bindings.generated.ts +17 -0
- package/template/agent/access/credential-bindings.ts +28 -0
- package/template/agent/access/grants.generated.ts +18 -0
- package/template/agent/access/grants.ts +30 -0
- package/template/agent/access/network-access.ts +32 -0
- package/template/agent/agent.ts +125 -0
- package/template/agent/blob/hosting.ts +42 -0
- package/template/agent/identity/authors.ts +79 -0
- package/template/agent/identity/groups-overlay.ts +24 -0
- package/template/agent/identity/groups.generated.ts +17 -0
- package/template/agent/identity/groups.ts +84 -0
- package/template/agent/identity/principals.ts +37 -0
- package/template/agent/identity/surfaces.ts +48 -0
- package/template/agent/instructions/access.ts +11 -0
- package/template/agent/instructions.md +295 -0
- package/template/agent/instrumentation.ts +6 -0
- package/template/agent/lib/memory-grants.ts +52 -0
- package/template/agent/lib/register-adapters.ts +286 -0
- package/template/agent/lib/register-config.ts +185 -0
- package/template/agent/memory/config/agents-overlay.ts +26 -0
- package/template/agent/memory/config/agents.generated.ts +21 -0
- package/template/agent/memory/config/domains/dm.ts +10 -0
- package/template/agent/memory/config/domains/index.ts +125 -0
- package/template/agent/memory/config/domains/org.ts +27 -0
- package/template/agent/memory/config/domains.ts +165 -0
- package/template/agent/model/models.ts +60 -0
- package/template/agent/model/policy.ts +19 -0
- package/template/agent/model/stacks.ts +75 -0
- package/template/agent/plugins/generated/index.ts +13 -0
- package/template/agent/plugins/registry.ts +16 -0
- package/template/agent/plugins/skill-content.ts +13 -0
- package/template/agent/roles/roles.ts +146 -0
- package/template/agent/routines/collectors.ts +89 -0
- package/template/agent/sandbox/backend-select.ts +16 -0
- package/template/agent/sandbox/sandbox.ts +242 -0
- package/template/agent/sandbox/workspace/.keep +6 -0
- package/template/agent/settings/settings.ts +19 -0
- package/template/agent/spend/budgets.ts +25 -0
- package/template/agent/spend/checkpoints.ts +51 -0
- package/template/agent/spend/model-pricing.ts +25 -0
- package/template/agent/webhooks/hooks-overlay.ts +34 -0
- package/template/agent/webhooks/hooks.generated.ts +17 -0
- package/template/agent/webhooks/hooks.ts +133 -0
- package/template/claude/skills/quickstart-open-everywhere/SKILL.md +167 -0
- package/template/claude/skills/upgrade-open-everywhere/SKILL.md +160 -0
- package/template/gitignore +42 -0
- package/template/package.json +62 -0
- package/template/roster.config.ts +39 -0
- package/template/roster.lock.json +182 -0
- package/template/scripts/env-pipe.mjs +74 -0
- package/template/scripts/ops-mcp.ts +35 -0
- package/template/tsconfig.json +23 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
This directory is eve's sandbox WORKSPACE SEED: eve copies its whole subtree into /workspace
|
|
2
|
+
inside every sandbox before any code runs. An EMPTY seed mints no sandbox template, so every
|
|
3
|
+
session starts cold. This one file keeps the template, and a dotfile keeps /workspace clean.
|
|
4
|
+
|
|
5
|
+
Do not put anything here that a running session is supposed to own (memory in particular): the
|
|
6
|
+
seed is static, and the same bytes reach every session.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// This deployment's behaviour settings. Everything the agent can be opinionated about — which walls
|
|
2
|
+
// are up, which apparatus is armed, what the caps are — is typed code here, so a behaviour change
|
|
3
|
+
// is a pull request against this repo.
|
|
4
|
+
//
|
|
5
|
+
// EMPTY IS A REAL ANSWER: with nothing set, you run the shipped defaults. The schema (every
|
|
6
|
+
// setting, its default and its validation) is `@open-everywhere/core/agent/settings/settings.ts`,
|
|
7
|
+
// and docs/deploy/configuration-reference.md in the Open Everywhere repository carries the operator table. `defineSettings` validates
|
|
8
|
+
// at module load, so a typo fails the boot instead of doing nothing.
|
|
9
|
+
//
|
|
10
|
+
// Examples:
|
|
11
|
+
// identity: { name: "Ada" }, // the name the agent answers to
|
|
12
|
+
// browser: { enabled: true }, // arm the in-sandbox browser (hosts: agent/access/browser-hosts.ts)
|
|
13
|
+
//
|
|
14
|
+
// Not a secret store. Keys, tokens and database URLs stay in the environment or the encrypted
|
|
15
|
+
// credential store.
|
|
16
|
+
|
|
17
|
+
import { defineSettings } from "@open-everywhere/core/agent/settings/settings.js";
|
|
18
|
+
|
|
19
|
+
export const SETTINGS_OVERRIDES = defineSettings({});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Spend budgets — monthly caps, as a token count and/or a USD amount, per surface, per workspace,
|
|
2
|
+
// per domain, or as a blanket default. The narrowest entry wins for a scope. Enforcement is
|
|
3
|
+
// pre-flight at every new-spend origin (a mention, a routine fire, a webhook fire): an over-cap
|
|
4
|
+
// scope refuses new work until the month rolls over, the config is raised, or an admin approves a
|
|
5
|
+
// temporary override.
|
|
6
|
+
//
|
|
7
|
+
// EMPTY BY DEFAULT — nothing is capped until you declare a budget. `assertSpendBudgetsValid` runs
|
|
8
|
+
// at import, so `npm run typecheck` plus a boot catches a malformed entry.
|
|
9
|
+
//
|
|
10
|
+
// USD caps price each call by its model's rate in agent/spend/model-pricing.ts. A model with no
|
|
11
|
+
// rate there contributes $0, so give every model you select a rate, or set a token cap as well.
|
|
12
|
+
// See docs/security/spend-and-cost.md in the Open Everywhere repository.
|
|
13
|
+
|
|
14
|
+
import { defineSpendBudgets, assertSpendBudgetsValid, type SpendBudgets } from "@open-everywhere/core/agent/spend/spend.js";
|
|
15
|
+
|
|
16
|
+
const budgets: SpendBudgets = defineSpendBudgets({
|
|
17
|
+
// Example — cap one Slack channel at $20 a month:
|
|
18
|
+
// perSurface: { "slack/<team-id>/<channel-id>": { surfaceMonthlyUsd: 20 } },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
assertSpendBudgetsValid(budgets);
|
|
22
|
+
|
|
23
|
+
export function loadSpendBudgets(): SpendBudgets {
|
|
24
|
+
return budgets;
|
|
25
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Spend checkpoints — config-as-code for the SOFT session cost control (OPE-62,
|
|
2
|
+
// docs/security/spend-and-cost.md in the Open Everywhere repository). Declare per-SESSION thresholds — a token count, a
|
|
3
|
+
// model-call count, and/or an estimated USD amount — per surface, per workspace, per memory
|
|
4
|
+
// domain, or as a blanket default; the narrowest entry wins for a scope, and any component the
|
|
5
|
+
// winning entry sets is checked.
|
|
6
|
+
//
|
|
7
|
+
// DISTINCT FROM budgets.ts (the HARD monthly cap): a budget REFUSES new work outright when a
|
|
8
|
+
// scope is over its monthly total. A checkpoint is softer and per-session — when a single
|
|
9
|
+
// conversation gets expensive it PAUSES only CONSEQUENTIAL actions (PRs, memory writes, skill/
|
|
10
|
+
// routine creation, …) and posts a "keep going?" card. Reads and replies are never blocked, and a
|
|
11
|
+
// member's approval lets the agent resume for the rest of that session. It's a runaway-session
|
|
12
|
+
// circuit-breaker, not a spend ceiling.
|
|
13
|
+
//
|
|
14
|
+
// EMPTY BY DEFAULT — with nothing declared here, nothing is checkpointed (Open Everywhere never
|
|
15
|
+
// invents a threshold). This file is the seam a DB/dashboard would swap behind, same as
|
|
16
|
+
// budgets.ts / memory/config/domains.ts. After adding a checkpoint: `npm run typecheck:all`
|
|
17
|
+
// (assertSpendCheckpointsValid runs at import time and throws on a malformed entry — bad scope
|
|
18
|
+
// keys, unknown/typo'd fields, non-positive amounts).
|
|
19
|
+
//
|
|
20
|
+
// The USD component prices this session's usage_events rows per-row (each row's stamped model ×
|
|
21
|
+
// its verified/catalog rate — the same figure session_cost / spend_report show). A deploy whose
|
|
22
|
+
// models aren't priced (agent/spend/model-pricing.ts) never trips a USD checkpoint;
|
|
23
|
+
// use the token or model-call component there instead.
|
|
24
|
+
|
|
25
|
+
import { defineSpendCheckpoints, assertSpendCheckpointsValid, type SpendCheckpoints } from "@open-everywhere/core/agent/spend/checkpoint.js";
|
|
26
|
+
|
|
27
|
+
const checkpoints: SpendCheckpoints = defineSpendCheckpoints({
|
|
28
|
+
// --- EXAMPLE (illustrative numbers, not recommendations) ---
|
|
29
|
+
// default: {
|
|
30
|
+
// sessionTokens: 5_000_000, // pause consequential actions after ~5M tokens in one session
|
|
31
|
+
// sessionModelCalls: 200, // …or after ~200 model calls (the closest analogue to "turns")
|
|
32
|
+
// },
|
|
33
|
+
// perDomain: {
|
|
34
|
+
// // Keyed by a memory DOMAIN name (memory/domain.ts), same resolution as budgets.ts's perDomain.
|
|
35
|
+
// public: {
|
|
36
|
+
// sessionUsd: 2, // a guest/externally-shared session: confirm past ~$2
|
|
37
|
+
// },
|
|
38
|
+
// },
|
|
39
|
+
// perSurface: {
|
|
40
|
+
// "slack/<team-id>/<channel-id>": { // one channel that runs big coding sessions gets a higher bar
|
|
41
|
+
// sessionUsd: 10,
|
|
42
|
+
// },
|
|
43
|
+
// },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
assertSpendCheckpointsValid(checkpoints);
|
|
47
|
+
|
|
48
|
+
/** The seam a DB/dashboard would swap behind. Static config for now. */
|
|
49
|
+
export function loadSpendCheckpoints(): SpendCheckpoints {
|
|
50
|
+
return checkpoints;
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Model pricing — this deployment's $/Mtok table. A USD spend cap (agent/spend/budgets.ts) prices
|
|
2
|
+
// each call by its model's rate. A call with no rate contributes $0, so an unpriced model turns a
|
|
3
|
+
// USD cap into a guard that looks armed but is empty. Give every model your stacks can select a
|
|
4
|
+
// rate here. Core ships the rate of the shipped primary (`openai/gpt-6-luna`), so this file does
|
|
5
|
+
// not restate it.
|
|
6
|
+
//
|
|
7
|
+
// Without `cachedInPerMtok`, a cached read is priced at the full input rate, which can only
|
|
8
|
+
// overstate a cost. `assertModelPricingValid` runs at import.
|
|
9
|
+
|
|
10
|
+
import { defineModelPricing, assertModelPricingValid, type ModelPricingTable } from "@open-everywhere/core/agent/spend/pricing.js";
|
|
11
|
+
|
|
12
|
+
const pricing: ModelPricingTable = defineModelPricing({
|
|
13
|
+
// chinese-open-weights: primary, utility, supervisor and judge.
|
|
14
|
+
"deepseek/deepseek-v4-flash-0731": { inPerMtok: 0.14, outPerMtok: 0.28 },
|
|
15
|
+
// chinese-open-weights: vision.
|
|
16
|
+
"alibaba/qwen3-vl-instruct": { inPerMtok: 0.2, outPerMtok: 0.88 },
|
|
17
|
+
// american-closed: vision.
|
|
18
|
+
"google/gemini-3.7-flash": { inPerMtok: 0.75, outPerMtok: 3.75 },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
assertModelPricingValid(pricing);
|
|
22
|
+
|
|
23
|
+
export function loadModelPricing(): ModelPricingTable {
|
|
24
|
+
return pricing;
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The webhook-hooks OVERLAY loader — the runtime half of the `declare_webhook` flow (OPE-208).
|
|
2
|
+
// `hooks.generated.ts` (next to this file) is a MACHINE-OWNED, DATA-ONLY overlay of extra hook
|
|
3
|
+
// declarations that merges into the config-as-code baseline in `hooks.ts` at load. Nothing edits it
|
|
4
|
+
// by hand in the normal flow: the deterministic codemod (core `webhooks/hooks-codemod.ts`) writes it
|
|
5
|
+
// on a `webhook-declare/<id>` branch, a bot-PR carries the diff, and GitHub's merge button is the
|
|
6
|
+
// approval gate.
|
|
7
|
+
//
|
|
8
|
+
// Deliberately the same shape, the same import style and the same fail-loud posture as
|
|
9
|
+
// `agent/access/grants.ts`, because it is the same flow with a different file: a static import so
|
|
10
|
+
// Eve's bundler inlines the overlay into the deploy artifact (a declaration that merged is a
|
|
11
|
+
// declaration that ships, rather than a file path that could be silently stripped), and STRICT
|
|
12
|
+
// parsing so a malformed overlay refuses to boot rather than loading as "roughly right". Vercel keeps
|
|
13
|
+
// the previous deployment live on a failed build, so the worst case is a red pipeline.
|
|
14
|
+
//
|
|
15
|
+
// The DATA lives in a `.ts` module rather than JSON (OPE-256) — one authoring language for the whole
|
|
16
|
+
// deployer config. That matters most here of the three overlays: a hook is a STANDING UNATTENDED
|
|
17
|
+
// PROMPT, so "the file cannot contain code" is worth something. A module IS executable at load, and
|
|
18
|
+
// the parse below only sees the value, so this loader does not prove the file is data. In this app no
|
|
19
|
+
// check before the merge does: whoever merges the codemod PR is the only gate. Review it as code.
|
|
20
|
+
//
|
|
21
|
+
// 🔴 IT HOLDS NO SECRETS AND CANNOT. Each entry carries `secretRef` + `broker` — a pointer and the
|
|
22
|
+
// home it resolves through. Setting the VALUE is an operator's separate act (an env var, or a write
|
|
23
|
+
// into the deployment's own vault), which is what keeps a webhook's shared secret out of every diff,
|
|
24
|
+
// every transcript and every log.
|
|
25
|
+
|
|
26
|
+
import { parseHooksFile, type HooksFile } from "@open-everywhere/core/agent/webhooks/hooks-codemod.js";
|
|
27
|
+
import hooksOverlay from "./hooks.generated.js";
|
|
28
|
+
|
|
29
|
+
const overlay: HooksFile = parseHooksFile(hooksOverlay);
|
|
30
|
+
|
|
31
|
+
/** The parsed hooks overlay — merged into loadWebhooks() by hooks.ts. */
|
|
32
|
+
export function loadHooksOverlay(): HooksFile {
|
|
33
|
+
return overlay;
|
|
34
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// GENERATED + MACHINE-OWNED — DO NOT HAND-EDIT.
|
|
2
|
+
//
|
|
3
|
+
// The webhook-hooks overlay: DATA ONLY. Written exclusively by the deterministic codemod
|
|
4
|
+
// (packages/core/agent/webhooks/hooks-codemod.ts) on a bot-PR branch; the PR merge is the approval gate.
|
|
5
|
+
//
|
|
6
|
+
// The codemod re-emits this file from its own parsed content and refuses to rewrite it when the
|
|
7
|
+
// bytes differ, so a hand-edit stops the next machine PR. Before a merge, nothing checks that the
|
|
8
|
+
// file is still data only unless the repository runs its own check: the person who merges the PR
|
|
9
|
+
// is the gate. Read a PR to this file as code, because it is code.
|
|
10
|
+
//
|
|
11
|
+
// To change it by hand, edit only the data below and keep the layout exactly as emitted.
|
|
12
|
+
|
|
13
|
+
import type { HooksFile } from "@open-everywhere/core/agent/webhooks/hooks-codemod.js";
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
"hooks": []
|
|
17
|
+
} satisfies HooksFile;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Webhook-trigger configuration — config-as-code, the admin console for the generic
|
|
2
|
+
// inbound-webhook surface (docs/capabilities/proactivity-and-routines.md in the Open Everywhere repository "Inbound
|
|
3
|
+
// webhook triggers"). Declare a hook: which signature scheme its sender uses (`source`),
|
|
4
|
+
// where its shared secret lives (`secretRef` + the `broker` that ref resolves through),
|
|
5
|
+
// which `slack/TEAM/CHANNEL` surface it fires into, and what the agent should do when it
|
|
6
|
+
// fires. Point the provider at `POST /webhooks/<id>` (channels/webhooks.ts).
|
|
7
|
+
//
|
|
8
|
+
// SIGNATURE SCHEME: `source` names the registered verifier. `"oe-standard-webhook"` is
|
|
9
|
+
// OUR own contract — the public standard-webhooks spec (`webhook-id` + `webhook-timestamp`
|
|
10
|
+
// + `webhook-signature: v1,<base64>` over `<id>.<ts>.<rawBody>`, 300s skew) — right for a
|
|
11
|
+
// sender you configure (your CI, a script, a shim), and it has drop-in sender libraries in
|
|
12
|
+
// most languages. A THIRD-PARTY vendor signs in its own scheme and never in ours, so a
|
|
13
|
+
// vendor-fed hook names the vendor instead (`source: "github"`, `"notion"`, …) and is
|
|
14
|
+
// verified exactly the way that vendor signs. The source is DECLARED, never sniffed off
|
|
15
|
+
// the request; an unregistered source is a fail-closed 401 naming the registered set.
|
|
16
|
+
//
|
|
17
|
+
// ⚠️ `source: "oe-hmac"` WAS A SECOND OE-DEFINED SCHEME AND IS GONE (OPE-213). It carried
|
|
18
|
+
// no per-delivery id, so it had no replay defence at all — a captured delivery replayed
|
|
19
|
+
// successfully inside the skew window. Any hook still declaring it now 401s with the
|
|
20
|
+
// registered set named; move the sender to `oe-standard-webhook`.
|
|
21
|
+
//
|
|
22
|
+
// SECRET CUSTODY (OPE-158): `broker: "env"` + a ref that is an env var NAME is the
|
|
23
|
+
// zero-wiring floor. `broker: "db"` instead resolves the ref out of our own AES vault
|
|
24
|
+
// (armed by OE_CREDENTIAL_ENCRYPTION_KEY) — the option to hold a hook's secret somewhere
|
|
25
|
+
// a config diff can't reach it. `secretRef` may also be an ORDERED LIST, current key
|
|
26
|
+
// first: a second entry is a rotation overlap, live until every sender has moved.
|
|
27
|
+
//
|
|
28
|
+
// WHO MAY READ IT (OPE-209): `readableBy` is REQUIRED on every hook and there is no default. It is
|
|
29
|
+
// a list of `access/bundle.ts` `Scope`s — `{ level: "domain", domain: "payments" }` names one domain,
|
|
30
|
+
// several entries are a UNION (many domains may legitimately read one hook), and `{ level: "org" }`
|
|
31
|
+
// means EVERY domain in the org. It is the ENTIRE confidentiality decision for this ingress: what a
|
|
32
|
+
// subscribing routine then ACTS on is that routine's own filter, and anything finer-grained than
|
|
33
|
+
// `readableBy` is a second webhook, not a cleverer grant. There is deliberately no way to omit it —
|
|
34
|
+
// an implicit default is how a hook came to be created readable by nobody, accepting deliveries and
|
|
35
|
+
// refusing every routine forever in silence (OPE-206).
|
|
36
|
+
//
|
|
37
|
+
// HOW MUCH OF THE PAYLOAD REACHES THE MODEL (OPE-214): `posture` is one axis with three points —
|
|
38
|
+
// `"trigger-only"` (nothing the sender wrote reaches the prompt; the instruction runs on the FACT
|
|
39
|
+
// that it fired), `"projection"` (only the field keys listed in `project`, and no entity is
|
|
40
|
+
// refetched), `"content-bearing"` (the entire raw body, as untrusted data, on every delivery,
|
|
41
|
+
// forever). OMITTING IT MEANS `content-bearing`, which is exactly what every config hook did before
|
|
42
|
+
// the field existed — so this was not a behaviour change for anything already deployed. Declare it
|
|
43
|
+
// explicitly on anything new; reach for `"projection"` whenever the instruction needs two fields
|
|
44
|
+
// rather than the whole body. Both of a hook's consequences take the same posture: its own declared
|
|
45
|
+
// fire AND its emission into the event plane, so a hook cannot disclose less where it was declared
|
|
46
|
+
// than in the rooms that subscribed to it. A `content-bearing` or `projection` fire is TAINTED (its
|
|
47
|
+
// prompt carries an outside party's bytes); a `trigger-only` one is not.
|
|
48
|
+
//
|
|
49
|
+
// EMPTY BY DEFAULT — with nothing declared here, every webhook id 404s. This file is
|
|
50
|
+
// the seam a DB/dashboard would swap behind, same as memory/config/domains.ts and access/
|
|
51
|
+
// bundles.ts. After adding a hook: `npm run typecheck` plus a boot (assertWebhooksValid runs
|
|
52
|
+
// at import time and throws on a malformed entry — id collisions, a missing source, a
|
|
53
|
+
// blank/empty secretRef, a missing broker, a target that isn't a well-formed
|
|
54
|
+
// slack/TEAM/CHANNEL triple).
|
|
55
|
+
|
|
56
|
+
import { defineWebhook, assertWebhooksValid, type WebhookHook } from "@open-everywhere/core/agent/webhooks/config.js";
|
|
57
|
+
import { loadHooksOverlay } from "./hooks-overlay.js";
|
|
58
|
+
import { loadDomains } from "../memory/config/domains.js";
|
|
59
|
+
|
|
60
|
+
const hooks: WebhookHook[] = [
|
|
61
|
+
// --- EXAMPLE: a CI pipeline posts a deploy-status summary into a Slack channel ---
|
|
62
|
+
// defineWebhook({
|
|
63
|
+
// id: "deploy-status", // POST /webhooks/deploy-status
|
|
64
|
+
// source: "oe-standard-webhook", // OUR contract — a sender you control signs it
|
|
65
|
+
// secretRef: "DEPLOY_STATUS_WEBHOOK_SECRET", // shared secret REF (env var NAME here, not the value)
|
|
66
|
+
// broker: "env", // the home that ref resolves through ("env" | "db" | ...)
|
|
67
|
+
// target: "slack/<team-id>/<channel-id>", // fully-qualified slack/TEAM/CHANNEL surface
|
|
68
|
+
// readableBy: [{ level: "domain", domain: "engineering" }], // WHO may read its events — REQUIRED, no default
|
|
69
|
+
// posture: "projection", // HOW MUCH of the payload reaches the model
|
|
70
|
+
// project: ["status", "branch"], // …under "projection": exactly these normalizer field keys
|
|
71
|
+
// instruction:
|
|
72
|
+
// "A CI deploy-status webhook just fired. Summarise the result for the channel " +
|
|
73
|
+
// "in one or two lines; only @-mention the channel if the deploy FAILED.",
|
|
74
|
+
// }),
|
|
75
|
+
// --- EXAMPLE: a THIRD-PARTY vendor's webhook, verified in THAT VENDOR's scheme ---
|
|
76
|
+
// The point of the verifier registry: GitHub signs `X-Hub-Signature-256` and will never
|
|
77
|
+
// adopt ours, so the hook names GitHub's source and the secret is the one GITHUB minted
|
|
78
|
+
// when the webhook was created. (Needs the github provider bundle selected — a
|
|
79
|
+
// de-selected bundle registers no verifier and the hook then 401s, fail-closed.)
|
|
80
|
+
// defineWebhook({
|
|
81
|
+
// id: "gh-deploys", // POST /webhooks/gh-deploys
|
|
82
|
+
// source: "github", // verified as GitHub signs, not as we do
|
|
83
|
+
// secretRef: "GH_DEPLOYS_WEBHOOK_SECRET", // the secret you pasted into GitHub's webhook form
|
|
84
|
+
// broker: "env",
|
|
85
|
+
// ...
|
|
86
|
+
// }),
|
|
87
|
+
// --- EXAMPLE: the SAME hook mid key-rotation, and one whose secret lives in the db vault ---
|
|
88
|
+
// defineWebhook({
|
|
89
|
+
// id: "deploy-status",
|
|
90
|
+
// source: "oe-standard-webhook",
|
|
91
|
+
// secretRef: ["DEPLOY_STATUS_WEBHOOK_SECRET_V2", "DEPLOY_STATUS_WEBHOOK_SECRET"], // CURRENT FIRST
|
|
92
|
+
// broker: "env", // ONE home for the whole hook — a rotation never moves custody
|
|
93
|
+
// ...
|
|
94
|
+
// }),
|
|
95
|
+
// defineWebhook({
|
|
96
|
+
// id: "deploy-status",
|
|
97
|
+
// source: "oe-standard-webhook",
|
|
98
|
+
// secretRef: "deploy-status-hook", // a stored-credential ref, not an env var
|
|
99
|
+
// broker: "db", // our own AES vault (needs OE_CREDENTIAL_ENCRYPTION_KEY armed)
|
|
100
|
+
// ...
|
|
101
|
+
// }),
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
// --- The hooks OVERLAY (declare_webhook flow, OPE-208) -----------------------
|
|
105
|
+
// `agent/webhooks/hooks.generated.ts` is a MACHINE-OWNED, DATA-ONLY overlay of extra declarations that a bot-PR adds and an
|
|
106
|
+
// admin merges (see hooks-overlay.ts). It merges here, and the MERGED list is what
|
|
107
|
+
// `assertWebhooksValid` checks — so a bad overlay (duplicate id, missing readableBy, a target that
|
|
108
|
+
// isn't a well-formed surface) is a fail-loud boot error rather than something that half-loads.
|
|
109
|
+
// Exactly the shape `access/bundles.ts` uses for the grants overlay, for the same reasons.
|
|
110
|
+
const merged: WebhookHook[] = [...hooks, ...loadHooksOverlay().hooks];
|
|
111
|
+
|
|
112
|
+
// ⭐ THE DOMAIN REGISTRY IS PASSED IN (OPE-215), which is what turns the validator from a SHAPE check
|
|
113
|
+
// into an ACHIEVABILITY check: a grant naming a domain this deployment does not declare is refused
|
|
114
|
+
// here rather than accepted and then silently refusing every routine forever, and a hook whose own
|
|
115
|
+
// target channel is in no domain (or in one the grant does not name) warns in the boot log.
|
|
116
|
+
//
|
|
117
|
+
// A DIRECT IMPORT, NOT `getConfig()`. This file runs at module import, before the app-config seam is
|
|
118
|
+
// necessarily armed; `memory/config/domains.ts` is a sibling static registry with no such ordering hazard.
|
|
119
|
+
// The parameter is optional and the sweep is INERT without it — a deployment that cannot supply the
|
|
120
|
+
// registry gets exactly the pre-OPE-215 validation rather than a boot failure.
|
|
121
|
+
assertWebhooksValid(merged, { domains: loadDomains() });
|
|
122
|
+
|
|
123
|
+
/** The seam a DB/dashboard would swap behind. Currently static config + the declaration overlay. */
|
|
124
|
+
export function loadWebhooks(): readonly WebhookHook[] {
|
|
125
|
+
return merged;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The CODE-ONLY baseline (overlay EXCLUDED) — what `approvals/webhook-declare.ts` merges a
|
|
129
|
+
* CANDIDATE overlay into for pre-PR re-validation, and what tells the codemod which ids a chat
|
|
130
|
+
* declaration must not collide with. Not for the route, which uses the merged list above. */
|
|
131
|
+
export function loadBaseWebhooks(): readonly WebhookHook[] {
|
|
132
|
+
return hooks;
|
|
133
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: quickstart-open-everywhere
|
|
3
|
+
description: Set up, configure or deploy an Open Everywhere app — from a fresh scaffold to an agent that answers in Slack. Use when the user wants to set up Open Everywhere, deploy it, or connect Slack, Notion or GitHub. Two routes are supported, Vercel and Docker, and step 1 is to ask which. Drives the open-everywhere-ops MCP server (preflight, surface_manifest, scaffold_env, scaffold_credential_key, secret_set/list/rm) and the `@open-everywhere/create --check --json` status report.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Quickstart Open Everywhere
|
|
7
|
+
|
|
8
|
+
This directory is an **Open Everywhere app** — either a scaffolded app (it depends on the
|
|
9
|
+
`@open-everywhere/*` npm packages and holds only configuration) or a clone of the Open Everywhere
|
|
10
|
+
repository, which contains those packages and is itself an app. `AGENTS.md` describes which one you
|
|
11
|
+
are in. Its `.mcp.json` gives you the `open-everywhere-ops` MCP server. You supply the judgement,
|
|
12
|
+
and you run commands **with the user's consent**.
|
|
13
|
+
|
|
14
|
+
🔴 **Two commands differ between the two homes.** In a scaffolded app, run
|
|
15
|
+
`npx @open-everywhere/create --check --json` and `npm run -s env:pipe`. In a clone of the
|
|
16
|
+
repository, run `node packages/create/bin/create-open-everywhere.js --check --json` (the checkout's
|
|
17
|
+
own CLI, not the published one) and use the `grep`/`cut` form of the env push given in step 9 —
|
|
18
|
+
that repository ships no `env:pipe` script. Everything else is the same.
|
|
19
|
+
|
|
20
|
+
## Golden rules
|
|
21
|
+
|
|
22
|
+
- The ops MCP tools check and scaffold. **You** run install, build and deploy commands in the
|
|
23
|
+
user's shell: show the command, get a yes, then run it. Never run a deploy, create a cloud
|
|
24
|
+
resource, or write a secret without explicit confirmation.
|
|
25
|
+
- **Never ask the user to paste a secret into the chat, and never echo or commit one.** They paste
|
|
26
|
+
it where a tool reads it; you write it to the gitignored `.env.local` with `scaffold_env`, then
|
|
27
|
+
forget it. Mint the credential-store key with `scaffold_credential_key`, which never prints it.
|
|
28
|
+
- **Never propose a local run as a setup step.** `npm run dev` is the development loop AFTER the
|
|
29
|
+
deploy. A local run has no public HTTPS URL for Slack to reach and keeps its state in a different
|
|
30
|
+
store, so it proves little about the deployment. See "Develop locally" below.
|
|
31
|
+
- **Where each secret lives.** The deployment reads `DATABASE_URL`,
|
|
32
|
+
`OE_CREDENTIAL_ENCRYPTION_KEY`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET` and
|
|
33
|
+
`GITHUB_WEBHOOK_SECRET` from its **environment**: the Slack bundle and the GitHub webhook check
|
|
34
|
+
never read the credential store. So these go in `.env.local`, and then into the deployment's own
|
|
35
|
+
environment. `secret_set` is for a credential whose `broker` is `"db"` (and
|
|
36
|
+
`NOTION_WEBHOOK_SECRET`). It needs `DATABASE_URL` and the key in `.env.local` first. Nothing
|
|
37
|
+
reads a stored secret back out (`secret_list` returns names only).
|
|
38
|
+
- Every command runs from the app root.
|
|
39
|
+
|
|
40
|
+
## Step 1 — ask which route
|
|
41
|
+
|
|
42
|
+
**Two routes are supported. Ask the user to pick one before anything else.**
|
|
43
|
+
|
|
44
|
+
- **Vercel** — on rails: the `vercel` CLI plus Marketplace Neon. The deployment authenticates to
|
|
45
|
+
the AI Gateway with OIDC, so it needs no gateway key.
|
|
46
|
+
- **Docker** — self-host a container that satisfies the **deployment contract**
|
|
47
|
+
(`https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/contract.md`). Off
|
|
48
|
+
Vercel there is no OIDC, so the model route needs `AI_GATEWAY_API_KEY`. Running a different model
|
|
49
|
+
backend instead is an adapter swap in app code, not an env setting
|
|
50
|
+
(`https://github.com/open-everywhere/open-everywhere/blob/main/docs/extending/adapter-model.md`),
|
|
51
|
+
and `--check` cannot see it.
|
|
52
|
+
|
|
53
|
+
Nothing beyond these two is supported. For any other host, read the contract and follow the Docker
|
|
54
|
+
route; do not invent a recipe.
|
|
55
|
+
|
|
56
|
+
## Status, at every step
|
|
57
|
+
|
|
58
|
+
- `npx @open-everywhere/create --check --json` — the step list as data: `satisfied`, `detail`, and a
|
|
59
|
+
`next` command per unsatisfied step. It changes nothing. Pass `--route docker` on the Docker
|
|
60
|
+
route, and `--host https://<host>` once a deployment exists. In a clone of the repository, run
|
|
61
|
+
`node packages/create/bin/create-open-everywhere.js --check --json` instead.
|
|
62
|
+
- The `preflight` tool — the env verdict per name. Pass `target: "vercel"` or `target: "docker"`.
|
|
63
|
+
|
|
64
|
+
Re-run both after each step. They are the only answer to "what is left to do": there is no plan
|
|
65
|
+
tool. The same step list, written for a human, is
|
|
66
|
+
`https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/setup-steps.md`.
|
|
67
|
+
|
|
68
|
+
## The Vercel route
|
|
69
|
+
|
|
70
|
+
1. **Install.** If `node_modules` is missing: `npm install`. Its `postinstall` runs `oe-eve-patch`,
|
|
71
|
+
which patches the installed `eve`. The app refuses to boot without the patch.
|
|
72
|
+
2. **Link.** `npx vercel link` (interactive: the user signs in). Every later `vercel` command acts
|
|
73
|
+
on the linked project.
|
|
74
|
+
3. **Database.** `npx vercel install neon` provisions a Neon database and connects it to the linked
|
|
75
|
+
project, which sets `DATABASE_URL` there. (`vercel install` is an alias of
|
|
76
|
+
`vercel integration add`; the Marketplace slug is `neon`.) The dashboard path — project →
|
|
77
|
+
**Storage → Neon → Connect** — does the same thing. Then get the same value into `.env.local`
|
|
78
|
+
for the operator tools, either with `npx vercel env pull` (it writes the project's environment
|
|
79
|
+
into `.env.local`, overwriting it, so pull before you add anything local) or by asking the user
|
|
80
|
+
for the connection string and writing it with `scaffold_env`. **That one value is the whole
|
|
81
|
+
step:** it resolves both the operational store and the memory backing to postgres on its own, so
|
|
82
|
+
set no provider selector. A selector pointing AWAY from postgres is the only thing that breaks
|
|
83
|
+
it.
|
|
84
|
+
4. **Key.** `scaffold_credential_key`.
|
|
85
|
+
5. **Slack app.** Call `surface_manifest { surface: "slack" }` and hand the user the manifest and
|
|
86
|
+
the steps it returns. Before the first deploy the manifest keeps `YOUR-DEPLOY-URL`. The user
|
|
87
|
+
creates and installs the app, then pastes `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET`: write
|
|
88
|
+
both with `scaffold_env`.
|
|
89
|
+
6. **Vouch the workspace.** Ask for the team id (`T…`: the segment after `/client/` in the
|
|
90
|
+
workspace URL at app.slack.com). In `agent/identity/surfaces.ts` add
|
|
91
|
+
`defineSurface({ key: "slack/<team-id>", trust: "internal" })`.
|
|
92
|
+
7. **Admin.** Ask for the user's member id (`U…`: Slack profile → ⋮ More → Copy member ID). In
|
|
93
|
+
`agent/memory/config/domains/org.ts` set `admins: ["slack/<team-id>/<member-id>"]`. Offer to
|
|
94
|
+
commit both edits. They take effect on the next deploy, so make them before it.
|
|
95
|
+
8. **Build.** `npm run build`. It must pass.
|
|
96
|
+
9. **Deployment env.** `.env.local` never ships, so the deployment reads only what you set ON it.
|
|
97
|
+
The names are `OE_CREDENTIAL_ENCRYPTION_KEY`, `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET`. The
|
|
98
|
+
Neon integration already set `DATABASE_URL` there, and that one value resolves both stores — so
|
|
99
|
+
there is no `OE_OPERATIONAL_PROVIDER` or `OE_MEMORY_PROVIDER` to push.
|
|
100
|
+
Confirm each command first, and **never let a value reach your own output.**
|
|
101
|
+
- **In a scaffolded app:** `npm run -s env:pipe -- <NAME> | npx vercel env add <NAME> production`.
|
|
102
|
+
Always pipe `env:pipe` straight into `vercel env add`, as written. Never run it bare: your
|
|
103
|
+
shell is never a terminal, so a bare run prints the secret into your output. `env:pipe`
|
|
104
|
+
refuses a file or a terminal, but it cannot tell a pipe into your own shell from a pipe into
|
|
105
|
+
`vercel`.
|
|
106
|
+
- **In a clone of the repository** (it ships no `env:pipe`):
|
|
107
|
+
`grep "^<NAME>=" .env.local | cut -d= -f2- | tr -d '\n' | npx vercel env add <NAME> production`.
|
|
108
|
+
Same rule: the value only ever crosses a pipe.
|
|
109
|
+
Do not push `AI_GATEWAY_API_KEY` (OIDC covers it), `OE_PUBLIC_BASE_URL` (only for a custom
|
|
110
|
+
domain) or `OE_INTAKE_PAGE_SIGNING_KEY` (the deployment mints it). A variable added after a
|
|
111
|
+
deploy reaches the functions only on the NEXT deploy.
|
|
112
|
+
10. **Deploy.** `npx vercel deploy --prod` (confirm first). Note the production host it prints. A
|
|
113
|
+
`git push` does NOT deploy.
|
|
114
|
+
11. **Slack URLs.** Call `surface_manifest { surface: "slack", baseUrl: "https://<host>" }`. The
|
|
115
|
+
user pastes it under the Slack app's **App Manifest** and saves. Slack verifies the Event
|
|
116
|
+
Subscriptions URL. If it fails, the user chooses **Retry** under Event Subscriptions.
|
|
117
|
+
12. **Prove it.** `npx @open-everywhere/create --check --json --host https://<host>`. The
|
|
118
|
+
`build-deploy` step is satisfied only when the deployed Slack route answers an unsigned request
|
|
119
|
+
with its own `401 unauthorized`. That proves boot and routing, not `SLACK_SIGNING_SECRET`: ask
|
|
120
|
+
the user to confirm Event Subscriptions shows **Verified**.
|
|
121
|
+
13. **Hello.** Ask the user to send the app a direct message in Slack.
|
|
122
|
+
|
|
123
|
+
## The Docker route
|
|
124
|
+
|
|
125
|
+
Read the deployment contract first: it states what a running Open Everywhere needs — Node 24, a
|
|
126
|
+
public HTTPS URL, Postgres, a sandbox backend, egress control, a model route, and the secrets list.
|
|
127
|
+
Then:
|
|
128
|
+
|
|
129
|
+
1. **Install and build.** `npm install`, then `npm run build`.
|
|
130
|
+
2. **Postgres.** Provision one the container can reach, and `scaffold_env` its `DATABASE_URL`. That
|
|
131
|
+
one value resolves both stores to postgres; set no provider selector.
|
|
132
|
+
3. **Key.** `scaffold_credential_key`.
|
|
133
|
+
4. **Model route.** There is no OIDC off Vercel, so the user supplies `AI_GATEWAY_API_KEY` (Vercel
|
|
134
|
+
→ AI Gateway → API Keys issues one for any host). Running a different model backend instead is
|
|
135
|
+
an **adapter swap in app code**
|
|
136
|
+
(`https://github.com/open-everywhere/open-everywhere/blob/main/docs/extending/adapter-model.md`),
|
|
137
|
+
which `--check` cannot see: that step stays `missing` on the Docker route either way, so read it
|
|
138
|
+
and move on rather than chasing a green it cannot give.
|
|
139
|
+
5. **Slack app.** Exactly as steps 5–7 of the Vercel route.
|
|
140
|
+
6. **Run it.** Build the image and run it on the server, with the contract's environment set and a
|
|
141
|
+
public HTTPS URL in front. The Open Everywhere repository's own `Dockerfile` and
|
|
142
|
+
`docker-compose.yml` are the starting point
|
|
143
|
+
(`https://github.com/open-everywhere/open-everywhere`); a scaffolded app ships neither, so copy
|
|
144
|
+
them from there.
|
|
145
|
+
7. **Prove it, then the Slack URLs and the hello.** As steps 11–13 of the Vercel route, with
|
|
146
|
+
`--route docker --host https://<host>`.
|
|
147
|
+
|
|
148
|
+
## Develop locally
|
|
149
|
+
|
|
150
|
+
Only when the user asks to change or debug the agent — never as a setup step. Write
|
|
151
|
+
`AI_GATEWAY_API_KEY` with `scaffold_env` (the user creates it: Vercel → AI Gateway → API Keys),
|
|
152
|
+
then `npm run dev`. A local run keeps its state in an embedded database under
|
|
153
|
+
`.open-everywhere-data/`, and Slack cannot reach it without a public tunnel.
|
|
154
|
+
|
|
155
|
+
## Notes
|
|
156
|
+
|
|
157
|
+
- `roster.config.ts` decides what the agent can do. After any change to it, or after a package
|
|
158
|
+
upgrade, run `npx oe build --accept` and show the user the `roster.lock.json` diff: accepting it
|
|
159
|
+
is their review decision.
|
|
160
|
+
- `.env.example` names every variable this app reads. Read it when unsure what a key does.
|
|
161
|
+
- The deployment's own identity (`deploymentIdentity`) is code in `agent/identity/surfaces.ts`, not
|
|
162
|
+
env.
|
|
163
|
+
- `@open-everywhere/create` has no interactive wizard and sends no telemetry. It scaffolds
|
|
164
|
+
(`npx @open-everywhere/create <dir>`) and reports status (`--check`). Nothing else.
|
|
165
|
+
- 🔴 **`--check` reads `.env.local` and your shell, never the deployment's own environment.** A
|
|
166
|
+
"9/9 satisfied" is not proof that the deployment holds the credential key or the Slack secrets:
|
|
167
|
+
step 9 is the only thing that puts them there, and nothing can observe it from here.
|