@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.
Files changed (82) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +69 -0
  4. package/bin/create-open-everywhere.js +57 -0
  5. package/package.json +57 -0
  6. package/src/cli.ts +241 -0
  7. package/src/deploy-probe.ts +99 -0
  8. package/src/engine.ts +106 -0
  9. package/src/env-file.ts +53 -0
  10. package/src/scaffold.ts +188 -0
  11. package/src/steps/build-deploy.ts +78 -0
  12. package/src/steps/database.ts +85 -0
  13. package/src/steps/env-checklist.ts +55 -0
  14. package/src/steps/index.ts +49 -0
  15. package/src/steps/prerequisites.ts +50 -0
  16. package/src/steps/repo.ts +69 -0
  17. package/src/steps/roster.ts +111 -0
  18. package/src/steps/slack.ts +74 -0
  19. package/src/steps/vercel.ts +40 -0
  20. package/src/steps/verify.ts +119 -0
  21. package/src/version.ts +20 -0
  22. package/template/.env.example +58 -0
  23. package/template/.mcp.json +8 -0
  24. package/template/AGENTS.md +62 -0
  25. package/template/CLAUDE.md +1 -0
  26. package/template/README.md +87 -0
  27. package/template/agent/access/access-requests.ts +69 -0
  28. package/template/agent/access/browser-hosts.ts +28 -0
  29. package/template/agent/access/bundles.ts +78 -0
  30. package/template/agent/access/connector-credentials.ts +74 -0
  31. package/template/agent/access/credential-bindings.generated.ts +17 -0
  32. package/template/agent/access/credential-bindings.ts +28 -0
  33. package/template/agent/access/grants.generated.ts +18 -0
  34. package/template/agent/access/grants.ts +30 -0
  35. package/template/agent/access/network-access.ts +32 -0
  36. package/template/agent/agent.ts +125 -0
  37. package/template/agent/blob/hosting.ts +42 -0
  38. package/template/agent/identity/authors.ts +79 -0
  39. package/template/agent/identity/groups-overlay.ts +24 -0
  40. package/template/agent/identity/groups.generated.ts +17 -0
  41. package/template/agent/identity/groups.ts +84 -0
  42. package/template/agent/identity/principals.ts +37 -0
  43. package/template/agent/identity/surfaces.ts +48 -0
  44. package/template/agent/instructions/access.ts +11 -0
  45. package/template/agent/instructions.md +295 -0
  46. package/template/agent/instrumentation.ts +6 -0
  47. package/template/agent/lib/memory-grants.ts +52 -0
  48. package/template/agent/lib/register-adapters.ts +286 -0
  49. package/template/agent/lib/register-config.ts +185 -0
  50. package/template/agent/memory/config/agents-overlay.ts +26 -0
  51. package/template/agent/memory/config/agents.generated.ts +21 -0
  52. package/template/agent/memory/config/domains/dm.ts +10 -0
  53. package/template/agent/memory/config/domains/index.ts +125 -0
  54. package/template/agent/memory/config/domains/org.ts +27 -0
  55. package/template/agent/memory/config/domains.ts +165 -0
  56. package/template/agent/model/models.ts +60 -0
  57. package/template/agent/model/policy.ts +19 -0
  58. package/template/agent/model/stacks.ts +75 -0
  59. package/template/agent/plugins/generated/index.ts +13 -0
  60. package/template/agent/plugins/registry.ts +16 -0
  61. package/template/agent/plugins/skill-content.ts +13 -0
  62. package/template/agent/roles/roles.ts +146 -0
  63. package/template/agent/routines/collectors.ts +89 -0
  64. package/template/agent/sandbox/backend-select.ts +16 -0
  65. package/template/agent/sandbox/sandbox.ts +242 -0
  66. package/template/agent/sandbox/workspace/.keep +6 -0
  67. package/template/agent/settings/settings.ts +19 -0
  68. package/template/agent/spend/budgets.ts +25 -0
  69. package/template/agent/spend/checkpoints.ts +51 -0
  70. package/template/agent/spend/model-pricing.ts +25 -0
  71. package/template/agent/webhooks/hooks-overlay.ts +34 -0
  72. package/template/agent/webhooks/hooks.generated.ts +17 -0
  73. package/template/agent/webhooks/hooks.ts +133 -0
  74. package/template/claude/skills/quickstart-open-everywhere/SKILL.md +167 -0
  75. package/template/claude/skills/upgrade-open-everywhere/SKILL.md +160 -0
  76. package/template/gitignore +42 -0
  77. package/template/package.json +62 -0
  78. package/template/roster.config.ts +39 -0
  79. package/template/roster.lock.json +182 -0
  80. package/template/scripts/env-pipe.mjs +74 -0
  81. package/template/scripts/ops-mcp.ts +35 -0
  82. 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.