naive-framework 0.1.0 → 0.3.0
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/AGENTS.md +2 -1
- package/docs/decisions/decisions.json +42 -0
- package/docs/reference/ROUTES.md +36 -37
- package/evidence/skills/fidelity-ladder-audit/ladder-census.mjs +2 -1
- package/governance/layer.json +2 -1
- package/governance/venture-template/roles/venture-roles.json +62 -0
- package/interface/acting.mjs +83 -0
- package/interface/adapters/claude-code.json +16 -0
- package/interface/adapters/codex.json +14 -0
- package/interface/adapters/cursor.json +16 -0
- package/interface/adapters.mjs +183 -0
- package/interface/api/server.mjs +4 -2
- package/interface/boundaries.mjs +219 -0
- package/interface/brand.mjs +215 -0
- package/interface/cli/freshness.mjs +59 -0
- package/interface/cli/guard.mjs +6 -0
- package/interface/cli/mentura.mjs +82 -11
- package/interface/cli/naive.mjs +44 -14
- package/interface/context.mjs +525 -0
- package/interface/demo/board.mjs +9 -8
- package/interface/demo/decision-harness.mjs +4 -3
- package/interface/dev/setup.mjs +84 -0
- package/interface/gate-hooks.mjs +37 -0
- package/interface/harness/evals.mjs +10 -9
- package/interface/harness/run.mjs +16 -15
- package/interface/instrumentation.mjs +87 -0
- package/interface/kit-boundary.mjs +73 -0
- package/interface/layer.json +8 -2
- package/interface/mcp/server.mjs +47 -7
- package/interface/package-routes.mjs +197 -0
- package/interface/provenance.mjs +28 -4
- package/interface/reads.mjs +174 -0
- package/interface/release-card.mjs +43 -0
- package/interface/router.mjs +110 -36
- package/interface/skills/naive-setup/SKILL.md +71 -0
- package/interface/skills/naive-setup/artifact.html +83 -0
- package/interface/vitals.mjs +116 -0
- package/interface/writes.mjs +190 -0
- package/knowledge/connectors/catalog.mjs +3 -2
- package/knowledge/connectors/filesystem.mjs +10 -5
- package/knowledge/connectors/index.mjs +3 -2
- package/knowledge/freshness.json +85 -0
- package/knowledge/layer.json +11 -3
- package/knowledge/templates/glossary.md +23 -0
- package/operations/layer.json +10 -3
- package/operations/marketing/check-public.mjs +7 -6
- package/operations/marketing/site/plan.mjs +4 -3
- package/operations/marketing/site/presence.mjs +4 -3
- package/operations/templates/marketing-brand.md +46 -0
- package/operations/templates/public-assets.md +39 -0
- package/package.json +3 -1
- package/packages/catalog.json +8 -0
- package/packages/customers/package.json +21 -1
- package/packages/deals/package.json +27 -0
- package/packages/decisions/package.json +12 -1
- package/packages/finance/frames/fund-without-ceding-control.frame.json +177 -0
- package/packages/foundation/package.json +8 -1
- package/packages/fund/package.json +48 -0
- package/packages/marketing/package.json +10 -4
- package/packages/money/package.json +24 -3
- package/packages/validation/package.json +13 -0
- package/platform/README.md +6 -0
- package/platform/adapter.json +15 -0
- package/platform/integrations/remote-image.mjs +79 -0
- package/platform/integrations/rho.mjs +9 -7
- package/platform/kits/README.md +8 -1
- package/platform/kits/agent-upgrade/kit.json +15 -0
- package/platform/kits/agent-upgrade/run.mjs +82 -0
- package/platform/kits/agent-upgrade/stand-in-agent.mjs +32 -0
- package/platform/kits/validation-kickoff/kit.json +29 -0
- package/platform/webhooks/server.mjs +70 -0
- package/purpose/frameworks/idea-map/ideamap.mjs +4 -2
- package/purpose/frameworks/idea-map/route.mjs +5 -4
- package/purpose/frameworks/product-anatomy/anatomy.mjs +2 -1
- package/schema/README.md +1 -0
- package/schema/access.mjs +58 -0
- package/schema/adapter.schema.json +33 -0
- package/schema/add-department.mjs +3 -2
- package/schema/assess-problem.mjs +3 -2
- package/schema/assets.mjs +213 -0
- package/schema/brand.mjs +374 -0
- package/schema/build-release.mjs +61 -0
- package/schema/caching.mjs +506 -0
- package/schema/check-assets.mjs +24 -0
- package/schema/check-brand.mjs +24 -0
- package/schema/check-config.mjs +111 -0
- package/schema/check-data-model.mjs +2 -1
- package/schema/check-data-packages.mjs +5 -4
- package/schema/check-execution.mjs +4 -3
- package/schema/check-nextjs-status.mjs +3 -1
- package/schema/check-people.mjs +84 -0
- package/schema/check-private.mjs +2 -1
- package/schema/check-ready.mjs +6 -5
- package/schema/check-sources.mjs +11 -2
- package/schema/check-where-used.mjs +4 -3
- package/schema/data-model.json +334 -8
- package/schema/deal.schema.json +80 -0
- package/schema/decision-analytics.mjs +2 -1
- package/schema/decisions.mjs +21 -12
- package/schema/examples/frames/judge.mjs +10 -0
- package/schema/examples/venture-type/jev.mjs +12 -0
- package/schema/examples/workspace/ventures/acme-labs/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/acme-labs/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/discovery-goal.decision.json +23 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/statement-icp.decision.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/statement-problem.decision.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/decisions/venture.team.decision.json +37 -0
- package/schema/examples/workspace/ventures/acme-labs/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/acme-labs/knowledge/sources.json +46 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000001.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000002.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000003.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000004.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000005.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000006.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000007.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000008.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000009.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000010.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000011.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000012.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000013.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000014.json +12 -0
- package/schema/examples/workspace/ventures/acme-labs/money/_streams/transactions/stream.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/money/_summary.json +30 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/customer-service/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/customer-service/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/it/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/it/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/marketing-and-sales/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/marketing-and-sales/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/products/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/products/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/acme-labs/people/founders/maya-chen.json +15 -0
- package/schema/examples/workspace/ventures/acme-labs/people/people.json +22 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/acme-labs/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.core-product.decision.json +28 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.exists.decision.json +38 -0
- package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.flagship.decision.json +27 -0
- package/schema/examples/workspace/ventures/acme-labs/profile.md +12 -0
- package/schema/examples/workspace/ventures/acme-labs/provenance.jsonl +12 -0
- package/schema/examples/workspace/ventures/acme-labs/raw/dump.md +6 -0
- package/schema/examples/workspace/ventures/acme-labs/venture.json +27 -0
- package/schema/examples/workspace/ventures/northstar/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/northstar/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/deals/ductline.json +8 -0
- package/schema/examples/workspace/ventures/northstar/deals/kilnworks.json +14 -0
- package/schema/examples/workspace/ventures/northstar/decisions/venture.team.decision.json +31 -0
- package/schema/examples/workspace/ventures/northstar/evidence/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/terms.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/terms.first-close.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/fund/decisions/thesis.decision.json +26 -0
- package/schema/examples/workspace/ventures/northstar/fund/terms.json +22 -0
- package/schema/examples/workspace/ventures/northstar/fund/thesis.json +22 -0
- package/schema/examples/workspace/ventures/northstar/governance/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/external-relationships/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/external-relationships/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/finance/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/human-capital/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/risk-and-compliance/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/risk-and-compliance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/strategy/charter.md +5 -0
- package/schema/examples/workspace/ventures/northstar/governance/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/operations/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/external-relationships/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/external-relationships/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/risk-and-compliance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/risk-and-compliance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/people/founders/ana-ruiz.json +13 -0
- package/schema/examples/workspace/ventures/northstar/people/founders/ben-okafor.json +7 -0
- package/schema/examples/workspace/ventures/northstar/people/people.json +27 -0
- package/schema/examples/workspace/ventures/northstar/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/northstar/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/northstar/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/AGENTS.md +17 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/accounts/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/decisions/venture.team.decision.json +31 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/knowledge/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/customer-service/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/customer-service/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/finance/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/finance/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/human-capital/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/human-capital/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/it/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/it/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/marketing-and-sales/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/marketing-and-sales/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/products/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/products/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/strategy/README.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/strategy/department.json +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/brand.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/business.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/product.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.core-product.decision.json +28 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.exists.decision.json +32 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.flagship.decision.json +27 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/profile.md +5 -0
- package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/venture.json +27 -0
- package/schema/examples/workspace/ventures/northstar/products/.gitkeep +0 -0
- package/schema/examples/workspace/ventures/northstar/profile.md +5 -0
- package/schema/examples/workspace/ventures/northstar/provenance.jsonl +3 -0
- package/schema/examples/workspace/ventures/northstar/purpose/README.md +3 -0
- package/schema/examples/workspace/ventures/northstar/venture.json +49 -0
- package/schema/foundation.mjs +8 -7
- package/schema/founder.schema.json +72 -0
- package/schema/frame.schema.json +240 -0
- package/schema/frames.mjs +229 -0
- package/schema/freshness-log.schema.json +40 -0
- package/schema/freshness-profiles.schema.json +44 -0
- package/schema/freshness.mjs +474 -0
- package/schema/fund-terms.schema.json +107 -0
- package/schema/fund-thesis.schema.json +87 -0
- package/schema/gate-drafts.mjs +131 -0
- package/schema/gate.mjs +105 -11
- package/schema/glossary.schema.json +25 -0
- package/schema/init-venture.mjs +69 -21
- package/schema/judgment.schema.json +38 -0
- package/schema/kit.schema.json +47 -0
- package/schema/kits.mjs +24 -0
- package/schema/knowledge-sources.schema.json +145 -15
- package/schema/layer.json +16 -3
- package/schema/marketing-brand.schema.json +116 -0
- package/schema/migrate-venture.mjs +8 -7
- package/schema/migrations/kit-to-package.mjs +95 -0
- package/schema/migrations/package-layout.mjs +90 -0
- package/schema/model-steps.mjs +718 -0
- package/schema/money-summary.schema.json +138 -0
- package/schema/naive-config.mjs +166 -0
- package/schema/naive-config.schema.json +268 -0
- package/schema/need-now.json +171 -0
- package/schema/need-now.mjs +8 -6
- package/schema/packages.mjs +21 -15
- package/schema/payouts.mjs +3 -2
- package/schema/people.schema.json +85 -0
- package/schema/port-venture.mjs +206 -132
- package/schema/public-assets.schema.json +76 -0
- package/schema/release-read.mjs +112 -0
- package/schema/releases.mjs +498 -0
- package/schema/run-handoff.mjs +5 -4
- package/schema/setup.mjs +165 -0
- package/schema/stale.mjs +112 -0
- package/schema/start-release.mjs +41 -0
- package/schema/structure.mjs +187 -0
- package/schema/systems-catalog.json +126 -0
- package/schema/systems-catalog.schema.json +78 -0
- package/schema/time-to-ready.mjs +7 -6
- package/schema/trigger.schema.json +44 -0
- package/schema/triggers.mjs +238 -0
- package/schema/upgrade-registry.schema.json +60 -0
- package/schema/upgrade-venture.mjs +96 -118
- package/schema/upgrades.json +69 -0
- package/schema/upgrades.mjs +386 -0
- package/schema/validate.py +19 -10
- package/schema/venture-paths.mjs +85 -0
- package/schema/venture-role.schema.json +64 -0
- package/schema/venture-type.mjs +62 -0
- package/schema/venture-types.json +346 -0
- package/schema/venture-types.schema.json +129 -0
- package/schema/venture.schema.json +61 -0
- package/schema/workspace.schema.json +5 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fetches a remote image for a venture (J-3), the platform half of Next.js remotePatterns. The
|
|
3
|
+
// framework records a remote image as a link with a note in raw/ (interface/brand.mjs addRemote);
|
|
4
|
+
// this fetcher is the only part that reaches the network, so it lives in platform/ (EE-041).
|
|
5
|
+
//
|
|
6
|
+
// - The host must be in the venture's remote_hosts (public/assets.json): the list of hosts an
|
|
7
|
+
// agent may fetch from. A redirect to another host is checked again; any other host is refused.
|
|
8
|
+
// - The original is kept private at raw/sources/<id>/original.<ext>, which stays out of git.
|
|
9
|
+
// - A full copy goes to public/ only with --copy, and only when the license allows a copy.
|
|
10
|
+
// - Facts (type, width, height, bytes, sha256) are read from the bytes by code (schema/assets.mjs).
|
|
11
|
+
//
|
|
12
|
+
// node platform/integrations/remote-image.mjs <url> --venture <id> --license <id> --credit <who>
|
|
13
|
+
// [--id <raw id>] [--alt <text>] [--copy public/<file>] [--workspace <dir>] [--json]
|
|
14
|
+
|
|
15
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join, resolve } from "node:path";
|
|
17
|
+
import { ventureDir } from "../../interface/router.mjs";
|
|
18
|
+
import { addAsset, addRemote } from "../../interface/brand.mjs";
|
|
19
|
+
import { allowsCopy, factsOf, hostAllowed, IMAGE, registerOf } from "../../schema/assets.mjs";
|
|
20
|
+
|
|
21
|
+
const MAX_BYTES = 20 * 1024 * 1024;
|
|
22
|
+
const EXT = { png: "png", gif: "gif", jpeg: "jpg", webp: "webp", ico: "ico", svg: "svg" };
|
|
23
|
+
|
|
24
|
+
/** Fetches url, following at most three redirects, each to a host the venture allows. */
|
|
25
|
+
export async function fetchAllowed(url, hosts, { fetchImpl = fetch } = {}) {
|
|
26
|
+
let at = url;
|
|
27
|
+
for (let hop = 0; hop < 4; hop++) {
|
|
28
|
+
const u = new URL(at);
|
|
29
|
+
if (!/^https?:$/.test(u.protocol)) throw new Error(`${at}: only http and https are fetched`);
|
|
30
|
+
if (!hostAllowed(u.host, hosts)) throw new Error(`${u.host} is not in remote_hosts, the hosts an agent may fetch from; a person adds it with mentura assets hosts`);
|
|
31
|
+
const res = await fetchImpl(at, { redirect: "manual" });
|
|
32
|
+
if (res.status >= 300 && res.status < 400 && res.headers.get("location")) { at = new URL(res.headers.get("location"), at).href; continue; }
|
|
33
|
+
if (!res.ok) throw new Error(`${at}: HTTP ${res.status}`);
|
|
34
|
+
const size = Number(res.headers.get("content-length") ?? 0);
|
|
35
|
+
if (size > MAX_BYTES) throw new Error(`${at}: ${size} bytes is more than ${MAX_BYTES}`);
|
|
36
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
37
|
+
if (buf.length > MAX_BYTES) throw new Error(`${at}: more than ${MAX_BYTES} bytes`);
|
|
38
|
+
return { url: at, buf };
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`${url}: too many redirects`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function fetchRemoteImage({ venture, workspace, url, license, credit, id, alt, copy, actor = "agent", fetchImpl }) {
|
|
44
|
+
const dir = ventureDir(venture, workspace);
|
|
45
|
+
if (!dir) return { ok: false, error: `no venture ${venture}` };
|
|
46
|
+
if (!license) return { ok: false, error: "say the image's license: --license <id>" };
|
|
47
|
+
if (copy && !copy.startsWith("public/")) return { ok: false, error: `${copy} is not in public/` };
|
|
48
|
+
if (copy && !allowsCopy(license)) return { ok: false, error: `license ${license} does not allow a full copy; link to the image instead (leave out --copy)` };
|
|
49
|
+
if (copy && existsSync(join(dir, copy))) return { ok: false, error: `${copy} is already there` };
|
|
50
|
+
const hosts = registerOf(dir)?.remote_hosts ?? [];
|
|
51
|
+
let got;
|
|
52
|
+
try { got = await fetchAllowed(url, hosts, { fetchImpl }); } catch (e) { return { ok: false, error: String(e.message ?? e) }; }
|
|
53
|
+
const f = factsOf(got.buf);
|
|
54
|
+
if (!IMAGE.has(f.type)) return { ok: false, error: `${url} is ${f.type}, not an image` };
|
|
55
|
+
const fetched = Object.fromEntries(Object.entries({ type: f.type, width: f.width, height: f.height, sizes: f.sizes, bytes: f.bytes, sha256: f.sha256, filled_at: new Date().toISOString() }).filter(([, v]) => v !== undefined));
|
|
56
|
+
if (copy) {
|
|
57
|
+
if (!copy.toLowerCase().endsWith(`.${EXT[f.type]}`) && !(f.type === "jpeg" && /\.jpe?g$/i.test(copy))) return { ok: false, error: `${copy}: the image is ${f.type}; name it .${EXT[f.type]}` };
|
|
58
|
+
mkdirSync(dirname(join(dir, copy)), { recursive: true });
|
|
59
|
+
writeFileSync(join(dir, copy), got.buf);
|
|
60
|
+
const a = await addAsset({ venture, workspace, path: copy, source: url, license, credit, alt, origin_url: url, actor });
|
|
61
|
+
if (!a.ok) return a;
|
|
62
|
+
}
|
|
63
|
+
const r = await addRemote({ venture, workspace, url, license, credit, id, alt, fetched, copy, actor });
|
|
64
|
+
if (!r.ok) return r;
|
|
65
|
+
// The original stays private: raw/sources/<id>/original.* is ignored by git (EE-070).
|
|
66
|
+
const original = join(r.result.raw, `original.${EXT[f.type]}`);
|
|
67
|
+
writeFileSync(join(dir, original), got.buf);
|
|
68
|
+
return { ok: true, result: { ...r.result, original, facts: fetched } };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
72
|
+
const argv = process.argv.slice(2);
|
|
73
|
+
const opt = (n) => { const i = argv.indexOf(`--${n}`); return i >= 0 ? argv[i + 1] : undefined; };
|
|
74
|
+
const url = argv.find((a, i) => !a.startsWith("--") && !(i > 0 && argv[i - 1].startsWith("--") && argv[i - 1] !== "--json"));
|
|
75
|
+
if (!url || !opt("venture")) { console.error("usage: node platform/integrations/remote-image.mjs <url> --venture <id> --license <id> --credit <who> [--id <raw id>] [--alt <text>] [--copy public/<file>]"); process.exit(2); }
|
|
76
|
+
const r = await fetchRemoteImage({ venture: opt("venture"), workspace: opt("workspace") ? resolve(opt("workspace")) : undefined, url, license: opt("license"), credit: opt("credit"), id: opt("id"), alt: opt("alt"), copy: opt("copy"), actor: opt("as") === "person" ? "person" : "agent" });
|
|
77
|
+
if (!r.ok) { console.error(r.error); process.exit(1); }
|
|
78
|
+
console.log(argv.includes("--json") ? JSON.stringify(r.result, null, 2) : `fetched ${url}: ${r.result.facts.type} ${r.result.facts.width ?? "?"}x${r.result.facts.height ?? "?"}, original kept private at ${r.result.original}${r.result.copy ? `, copy at ${r.result.copy}` : ""}`);
|
|
79
|
+
}
|
|
@@ -12,15 +12,16 @@
|
|
|
12
12
|
// --save-fixture keeps what Rho answered, for --fixture later
|
|
13
13
|
//
|
|
14
14
|
// The package it writes (data package shape, PR #23 stage C):
|
|
15
|
-
// money
|
|
15
|
+
// money/<rho id>.json master record, with a rho:account pointer
|
|
16
16
|
// money/_streams/transactions/stream.json sensitive: kept out of git and out of traces
|
|
17
17
|
// money/_streams/transactions/YYYY/MM/<date>-<id>.json append-only; a resync never rewrites one
|
|
18
|
-
// money/
|
|
18
|
+
// money/_summary.json versioned: cash, net burn, runway, as of when
|
|
19
19
|
//
|
|
20
20
|
// Amounts stay in minor units (cents for USD), as Rho returns them.
|
|
21
21
|
|
|
22
22
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
23
|
import { join } from "node:path";
|
|
24
|
+
import { ventureFile } from "../../schema/venture-paths.mjs";
|
|
24
25
|
|
|
25
26
|
export const SANDBOX = "https://rhoapi-sandbox.rho.co/mcp/v1";
|
|
26
27
|
export const LIVE = "https://rhoapi.rho.co/mcp/v1";
|
|
@@ -107,13 +108,14 @@ export function summarize(data, syncedAt) {
|
|
|
107
108
|
const json = (f, v) => { mkdirSync(join(f, ".."), { recursive: true }); writeFileSync(f, `${JSON.stringify(v, null, 2)}\n`); };
|
|
108
109
|
|
|
109
110
|
export function write(venture, data, syncedAt = new Date().toISOString()) {
|
|
110
|
-
const money = join(venture, "money");
|
|
111
|
+
const money = join(ventureFile(venture, "money"));
|
|
111
112
|
const pointer = (kind, id) => `${data.source}:${kind}:${id}`;
|
|
112
113
|
for (const a of data.accounts)
|
|
113
|
-
json(join(money,
|
|
114
|
+
json(join(money, `${a.id}.json`), { id: a.id, name: a.account_name, type: a.account_type, last_4: a.account_number_last_4 ?? null, balance: a.balance, source: pointer("account", a.id), synced_at: syncedAt });
|
|
114
115
|
const stream = join(money, "_streams/transactions");
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
// The stream's declaration, as schema/stream.schema.json has it. Its events are append-only and stay out of git.
|
|
117
|
+
json(join(stream, "stream.json"), { id: "transactions", sensitive: true,
|
|
118
|
+
summary: `Every transaction ${data.source} posted, one file each, as Rho returned it; Rho is the system of record. The versioned view is money/_summary.json.` });
|
|
117
119
|
let added = 0;
|
|
118
120
|
for (const t of data.transactions) {
|
|
119
121
|
const at = t.posted_at ?? t.initiated_at;
|
|
@@ -126,7 +128,7 @@ export function write(venture, data, syncedAt = new Date().toISOString()) {
|
|
|
126
128
|
const ignore = join(money, ".gitignore");
|
|
127
129
|
if (!existsSync(ignore)) writeFileSync(ignore, "_streams/transactions/*/\n");
|
|
128
130
|
const summary = summarize(data, syncedAt);
|
|
129
|
-
json(join(money, "
|
|
131
|
+
json(join(money, "_summary.json"), summary);
|
|
130
132
|
return { added, summary };
|
|
131
133
|
}
|
|
132
134
|
|
package/platform/kits/README.md
CHANGED
|
@@ -4,4 +4,11 @@ A kit belongs to Mentura. It is an enhanced package: automations, agent workflow
|
|
|
4
4
|
|
|
5
5
|
A kit lives at `platform/kits/<name>/kit.json`. It names the one package it extends (`package`) and never replaces that package. The package at `packages/<name>/package.json` works with no kit installed: schemas, templates, deterministic checks and at most one skill, offline, naming no vendor (EE-071).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A kit can also extend a framework command (`extends.command`), and bring automations: triggers that name the package they need and run only once it is complete (`schema/kit.schema.json`). A venture turns kits on with `kits` in its `naive.config.json`.
|
|
8
|
+
|
|
9
|
+
| Kit | What it does |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `agent-upgrade/` | Runs `naive upgrade --agent`: an agent does the guides' by-hand steps on a staged copy; the framework's checks and a typed confirmation decide what lands (U-3) |
|
|
12
|
+
| `validation-kickoff/` | Proposes the decision to start validating once the foundation is complete (M-12) |
|
|
13
|
+
|
|
14
|
+
`node platform/kits/test.mjs` tests them offline.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-upgrade",
|
|
3
|
+
"title": "Agent-run upgrade",
|
|
4
|
+
"purpose": "An agent works the upgrade of a venture (U-3, I-29): the framework's migrations run on a copy, the agent does what the version guides leave to a person (prose such as AGENTS.md), and the framework's checks decide. Nothing lands until the checks pass and the founder types the confirmation.",
|
|
5
|
+
"extends": { "command": "upgrade --agent" },
|
|
6
|
+
"entry": "run.mjs",
|
|
7
|
+
"agent": {
|
|
8
|
+
"command": ["claude", "-p", "--output-format", "json", "--permission-mode", "acceptEdits", "--allowedTools", "Read,Edit,Write,Glob,Grep", "--strict-mcp-config"],
|
|
9
|
+
"env": ["MENTURA_AGENT_CMD"]
|
|
10
|
+
},
|
|
11
|
+
"use_model": ["the agent edits the staged copy by the version guides' by-hand steps"],
|
|
12
|
+
"checks": ["schema/upgrades.mjs"],
|
|
13
|
+
"status": "built",
|
|
14
|
+
"note": "The agent sees only the staged copy, never _private/, and its claims are not trusted: every file it changed is found by hash and logged as an agent's write, and the framework's checks run after it. MENTURA_AGENT_CMD replaces the default agent (platform/kits/agent-upgrade/stand-in-agent.mjs is the offline stand-in the tests use)."
|
|
15
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The agent-run upgrade (U-3, I-29), a Mentura kit over the framework's staged upgrade. Next.js's
|
|
3
|
+
// `next upgrade --agent` has a coding agent perform and verify the upgrade; here the work is split the
|
|
4
|
+
// System 1 way. Code runs the migrations on a copy (schema/upgrades.mjs stage). The agent does only what
|
|
5
|
+
// the version guides leave to a person, in the copy. Code then finds every file the agent changed (by
|
|
6
|
+
// hash, not by its word), logs each as an agent's write, and runs the framework's checks. The upgrade
|
|
7
|
+
// stays a draft until the checks pass and the founder types the confirmation:
|
|
8
|
+
//
|
|
9
|
+
// node platform/kits/agent-upgrade/run.mjs <venture dir> [--json] (naive upgrade <dir> --agent)
|
|
10
|
+
// node interface/cli/naive.mjs upgrade <dir> --accept --confirm upgrade
|
|
11
|
+
//
|
|
12
|
+
// The agent is `claude -p` with file tools only, working in the copy, or MENTURA_AGENT_CMD (a command that
|
|
13
|
+
// reads the prompt on stdin, works in its current directory and answers as claude -p --output-format json).
|
|
14
|
+
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join, resolve } from "node:path";
|
|
18
|
+
import { agentFromEnv, record } from "../../../interface/provenance.mjs";
|
|
19
|
+
import { annotate, check, CONFIRM, snapshot, stage, STAGED } from "../../../schema/upgrades.mjs";
|
|
20
|
+
|
|
21
|
+
const repo = new URL("../../..", import.meta.url).pathname;
|
|
22
|
+
const kit = JSON.parse(readFileSync(new URL("./kit.json", import.meta.url), "utf8"));
|
|
23
|
+
const dirArg = process.argv[2];
|
|
24
|
+
const json = process.argv.includes("--json");
|
|
25
|
+
if (!dirArg || !existsSync(join(dirArg, "venture.json"))) { console.error("usage: node platform/kits/agent-upgrade/run.mjs <venture dir> [--json]"); process.exit(2); }
|
|
26
|
+
const dir = resolve(dirArg);
|
|
27
|
+
|
|
28
|
+
/** Runs the agent in the staged copy and returns its answer (an object), or throws saying why. */
|
|
29
|
+
export function runAgent(prompt, cwd) {
|
|
30
|
+
const cmd = process.env.MENTURA_AGENT_CMD ? process.env.MENTURA_AGENT_CMD.split(" ") : kit.agent.command;
|
|
31
|
+
// A relative program path in MENTURA_AGENT_CMD is relative to where naive was run, not to the copy.
|
|
32
|
+
const args = cmd.slice(1).map((a) => (/\.m?js$/.test(a) && !a.startsWith("/") && existsSync(resolve(a)) ? resolve(a) : a));
|
|
33
|
+
const p = spawnSync(cmd[0], args, { cwd, input: prompt, encoding: "utf8", maxBuffer: 1 << 26, env: { ...process.env, NAIVE_STAGE: cwd, NAIVE_TRIGGERS_ACTIVE: "upgrade" } });
|
|
34
|
+
if (p.error) throw new Error(`could not run the agent (${cmd[0]}): ${p.error.message}`);
|
|
35
|
+
let text = p.stdout;
|
|
36
|
+
try { const r = JSON.parse(p.stdout); if (r && typeof r.result === "string") { if (r.is_error) throw new Error(`the agent stopped: ${r.result}`); text = r.result; } }
|
|
37
|
+
catch (e) { if (e.message.startsWith("the agent stopped")) throw e; }
|
|
38
|
+
if (p.status !== 0) throw new Error(`the agent stopped with status ${p.status}: ${(p.stderr || text).trim().slice(0, 500)}${/login/i.test(p.stderr + text) ? " (run claude, then /login)" : ""}`);
|
|
39
|
+
const m = text.match(/\{[\s\S]*\}/);
|
|
40
|
+
try { return m ? JSON.parse(m[0]) : { summary: text.trim() }; } catch { return { summary: text.trim().slice(0, 2000) }; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const staged = await stage(dir, { by: kit.name }).catch((e) => { console.error(e.message); process.exit(1); });
|
|
44
|
+
const copy = join(dir, STAGED);
|
|
45
|
+
const guides = staged.guides.map((g) => `--- ${g}\n${readFileSync(join(repo, g), "utf8")}`).join("\n\n");
|
|
46
|
+
const prompt = [
|
|
47
|
+
`You are upgrading the venture in your current directory from Naive ${staged.from} to ${staged.to}. It is a copy: nothing you do reaches the venture until its founder accepts it.`,
|
|
48
|
+
"The framework's migrations have already run. Do only what the guides below leave to a person (their \"By hand\" sections) and what the migrations escalated, by editing files in this directory.",
|
|
49
|
+
"Rules: never open anything under _private/. Do not edit provenance.jsonl, naive.config.json, or anything under .release/ or .naive/. Do not delete files. Keep every JSON record valid against its schema. When something needs the founder's judgment, leave it and say so.",
|
|
50
|
+
`Escalated by the migrations:\n${staged.escalated.length ? staged.escalated.map((e) => `- ${e}`).join("\n") : "- nothing"}`,
|
|
51
|
+
`The guides:\n\n${guides || "(none)"}`,
|
|
52
|
+
'When you are done, answer with JSON only: {"summary": "<one sentence>", "edits": [{"path": "<file>", "why": "<the guide step>"}], "left": [{"what": "<item>", "why": "<why it needs the founder>"}]}',
|
|
53
|
+
].join("\n\n");
|
|
54
|
+
|
|
55
|
+
const before = snapshot(copy);
|
|
56
|
+
let answer;
|
|
57
|
+
try { answer = runAgent(prompt, copy); }
|
|
58
|
+
catch (e) {
|
|
59
|
+
annotate(dir, { agent: { kit: kit.name, error: e.message } });
|
|
60
|
+
const d = check(dir);
|
|
61
|
+
console.error(`${e.message}\nThe migrations' copy is staged without the agent's work (${d.status}). Accept it with --accept --confirm ${CONFIRM}, or --reject.`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
// What the agent changed, by hash. Each change is an agent's write in the copy's provenance.
|
|
65
|
+
const after = snapshot(copy);
|
|
66
|
+
const changed = Object.keys(after).filter((f) => f !== "provenance.jsonl" && after[f] !== before[f]).sort();
|
|
67
|
+
const deleted = Object.keys(before).filter((f) => !(f in after)).sort();
|
|
68
|
+
const who = agentFromEnv("agent", "cli", { id: kit.name, ...(process.env.MENTURA_AGENT_CMD ? {} : { harness: "Claude Code" }) });
|
|
69
|
+
const why = new Map((answer.edits ?? []).map((e) => [e.path, e.why]));
|
|
70
|
+
for (const f of changed) record(copy, { entity: f, activity: "write", agent: who, note: `agent-run upgrade to Naive ${staged.to}: ${why.get(f) ?? "changed by the agent (it did not say why)"}` });
|
|
71
|
+
annotate(dir, { agent: { kit: kit.name, summary: answer.summary ?? null, changed, deleted, reported: [...why.keys()], left: answer.left ?? [] } });
|
|
72
|
+
const d = check(dir);
|
|
73
|
+
|
|
74
|
+
if (json) { console.log(JSON.stringify(d, null, 2)); process.exit(d.status === "ready" ? 0 : 1); }
|
|
75
|
+
console.log(`staged upgrade of ${staged.from} to ${staged.to}, worked by an agent: ${d.status}`);
|
|
76
|
+
if (answer.summary) console.log(`agent ${answer.summary}`);
|
|
77
|
+
for (const f of changed) console.log(`edited ${f}${why.has(f) ? `: ${why.get(f)}` : " (not reported by the agent)"}`);
|
|
78
|
+
for (const f of deleted) console.log(`deleted ${f} (the agent was told not to delete; the founder decides)`);
|
|
79
|
+
for (const l of answer.left ?? []) console.log(`left ${l.what}: ${l.why}`);
|
|
80
|
+
for (const c of d.checks) console.log(`${c.passed ? "pass" : "FAIL"} ${c.check}${c.passed ? "" : `: ${c.output.split("\n").slice(-2).join(" / ")}`}`);
|
|
81
|
+
console.log(d.status === "ready" ? `Nothing has landed. To land it: node interface/cli/naive.mjs upgrade ${dirArg} --accept --confirm ${CONFIRM}` : `Nothing has landed, and it cannot until the checks pass. Fix the copy in ${dirArg}/${STAGED}, then run --status; or --reject.`);
|
|
82
|
+
process.exit(d.status === "ready" ? 0 : 1);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// A stand-in for the upgrade agent, for tests and docs that run offline
|
|
3
|
+
// (MENTURA_AGENT_CMD="node platform/kits/agent-upgrade/stand-in-agent.mjs"). It does the 0.3.0 guide's
|
|
4
|
+
// by-hand step the way an agent would: in every AGENTS.md of the copy it is working in (its current
|
|
5
|
+
// directory), it says package where the text says kit. It answers as claude -p --output-format json does.
|
|
6
|
+
// STAND_IN_BREAK=1 also breaks a record, so tests can show the checks stop a bad upgrade.
|
|
7
|
+
|
|
8
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
11
|
+
let prompt = "";
|
|
12
|
+
for await (const c of process.stdin) prompt += c;
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const edits = [];
|
|
15
|
+
const walk = (rel) => {
|
|
16
|
+
for (const e of readdirSync(join(cwd, rel), { withFileTypes: true })) {
|
|
17
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
18
|
+
if (e.isDirectory()) { if (!e.name.startsWith(".") && e.name !== "_private") walk(r); continue; }
|
|
19
|
+
if (e.name !== "AGENTS.md") continue;
|
|
20
|
+
const text = readFileSync(join(cwd, r), "utf8");
|
|
21
|
+
const next = text.replace(/\bkits\b/g, "packages").replace(/\bkit\b/g, "package");
|
|
22
|
+
if (next !== text) { writeFileSync(join(cwd, r), next); edits.push({ path: r, why: "0.3.0 by hand: say package where the text says kit" }); }
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
if (/say package where you wrote kit/.test(prompt)) walk("");
|
|
26
|
+
if (process.env.STAND_IN_BREAK === "1") {
|
|
27
|
+
mkdirSync(join(cwd, "foundation"), { recursive: true });
|
|
28
|
+
writeFileSync(join(cwd, "foundation/icp.json"), JSON.stringify({ broken: true }) + "\n");
|
|
29
|
+
edits.push({ path: "foundation/icp.json", why: "rewrote the ICP" });
|
|
30
|
+
}
|
|
31
|
+
const answer = { summary: edits.length ? `Updated ${edits.length} file(s) by the guides' by-hand steps.` : "Nothing in the guides' by-hand steps applied to this venture.", edits, left: [] };
|
|
32
|
+
console.log(JSON.stringify({ type: "result", is_error: false, result: JSON.stringify(answer) }));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "validation-kickoff",
|
|
3
|
+
"title": "Validation kickoff",
|
|
4
|
+
"purpose": "When the foundation is settled, start the next step: put the decision to begin validating in front of the founder (M-12). An automation over the validation package, so it waits until the foundation package it needs is complete.",
|
|
5
|
+
"extends": { "package": "validation" },
|
|
6
|
+
"automations": [
|
|
7
|
+
{
|
|
8
|
+
"id": "start",
|
|
9
|
+
"on": { "activity": ["write", "accept", "confirm"], "route": "foundation/**", "by": "person" },
|
|
10
|
+
"needs": "foundation",
|
|
11
|
+
"once": true,
|
|
12
|
+
"run": {
|
|
13
|
+
"propose": {
|
|
14
|
+
"id": "start-validation",
|
|
15
|
+
"question": "The foundation is settled. Start validating the solution now: write the solution record and plan the first problem interviews?",
|
|
16
|
+
"options": ["Yes: start validation now", "Not yet: keep working the foundation"],
|
|
17
|
+
"layer": "purpose",
|
|
18
|
+
"boundary": "fail-open",
|
|
19
|
+
"decides": { "record": "validation-solution", "path": "validation/solution.json" },
|
|
20
|
+
"unlocks": [{ "activity": "Draft the solution record and the interview plan", "routes": ["validation/solution"] }]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"note": "Proposes a decision rather than writing records: the founder decides whether validation starts."
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"use_model": [],
|
|
27
|
+
"status": "built",
|
|
28
|
+
"note": "Turn it on with \"kits\": [\"validation-kickoff\"] in the venture's naive.config.json. The framework's trigger runner (schema/triggers.mjs) runs it; this kit holds no code."
|
|
29
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Webhooks from systems of record (V-9, platform): a CRM, a bank or a spend tool calls Mentura when
|
|
3
|
+
// something it holds changes, the way a CMS calls a Next.js revalidation webhook. The request is checked
|
|
4
|
+
// (an HMAC-SHA256 signature of the raw body with the source's shared secret), and the framework marks
|
|
5
|
+
// every summary that reads the changed items stale (schema/stale.mjs notify). Nothing is fetched and no
|
|
6
|
+
// record is rewritten here: the next write of each summary makes it fresh.
|
|
7
|
+
//
|
|
8
|
+
// node platform/webhooks/server.mjs [--port 4330] [--host 127.0.0.1] [--workspace <dir>]
|
|
9
|
+
//
|
|
10
|
+
// POST /webhooks/<venture id>/<source id>
|
|
11
|
+
// header x-naive-signature: sha256=<hex HMAC-SHA256 of the raw body>
|
|
12
|
+
// body { "paths": ["customers/acme.json"], "version": "<the source's version, optional>", "at": "<when, optional>" }
|
|
13
|
+
// 202 { "venture", "source", "paths", "stale": [the files marked stale] }
|
|
14
|
+
//
|
|
15
|
+
// The secret is NAIVE_WEBHOOK_SECRET_<SOURCE> (the source id upper-cased, hyphens as underscores), else
|
|
16
|
+
// NAIVE_WEBHOOK_SECRET; with neither set every request is refused. A source must be in the venture's
|
|
17
|
+
// knowledge/sources.json. It binds to 127.0.0.1 by default; put it behind the platform's TLS to expose it.
|
|
18
|
+
|
|
19
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
20
|
+
import { createServer } from "node:http";
|
|
21
|
+
import { resolve } from "node:path";
|
|
22
|
+
import { ventureDir } from "../../interface/router.mjs";
|
|
23
|
+
import { notify } from "../../schema/stale.mjs";
|
|
24
|
+
|
|
25
|
+
const MAX = 256 * 1024;
|
|
26
|
+
export const secretFor = (source, env = process.env) => env[`NAIVE_WEBHOOK_SECRET_${source.toUpperCase().replace(/-/g, "_")}`] ?? env.NAIVE_WEBHOOK_SECRET ?? null;
|
|
27
|
+
export const sign = (secret, body) => `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
|
|
28
|
+
|
|
29
|
+
function verified(secret, body, header) {
|
|
30
|
+
if (!secret || typeof header !== "string") return false;
|
|
31
|
+
const want = Buffer.from(sign(secret, body)), got = Buffer.from(header);
|
|
32
|
+
return want.length === got.length && timingSafeEqual(want, got);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The webhook server over a workspace. env supplies the secrets (default process.env). */
|
|
36
|
+
export function webhooks({ workspace = process.env.MENTURA_WORKSPACE, env = process.env } = {}) {
|
|
37
|
+
return createServer((req, res) => {
|
|
38
|
+
const send = (code, body) => { res.writeHead(code, { "content-type": "application/json" }); res.end(JSON.stringify(body) + "\n"); };
|
|
39
|
+
const m = req.url?.match(/^\/webhooks\/([a-z0-9][a-z0-9-]*)\/([a-z][a-z0-9-]*)\/?$/);
|
|
40
|
+
if (!m) return send(404, { error: "POST /webhooks/<venture id>/<source id>" });
|
|
41
|
+
if (req.method !== "POST") return send(405, { error: "webhooks are POST" });
|
|
42
|
+
const chunks = [];
|
|
43
|
+
let size = 0;
|
|
44
|
+
req.on("data", (c) => { size += c.length; if (size > MAX) { send(413, { error: `a notification is at most ${MAX / 1024} KB` }); req.destroy(); } else chunks.push(c); });
|
|
45
|
+
req.on("end", () => {
|
|
46
|
+
if (size > MAX) return;
|
|
47
|
+
const raw = Buffer.concat(chunks);
|
|
48
|
+
const [, venture, source] = m;
|
|
49
|
+
if (!verified(secretFor(source, env), raw, req.headers["x-naive-signature"])) return send(401, { error: "the signature does not match this source's secret" });
|
|
50
|
+
const dir = ventureDir(venture, workspace);
|
|
51
|
+
if (!dir) return send(404, { error: `no venture ${venture}` });
|
|
52
|
+
let body;
|
|
53
|
+
try { body = JSON.parse(raw.toString("utf8")); } catch { return send(400, { error: "the body is not JSON" }); }
|
|
54
|
+
const paths = [body.paths ?? body.path].flat().filter((p) => typeof p === "string" && p && !p.startsWith("/") && !p.split("/").includes(".."));
|
|
55
|
+
if (!paths.length) return send(400, { error: "name the changed items in paths" });
|
|
56
|
+
try {
|
|
57
|
+
const r = notify(dir, { source, paths, version: typeof body.version === "string" ? body.version : undefined, at: typeof body.at === "string" ? body.at : undefined, via: "webhook" });
|
|
58
|
+
return send(202, { venture, ...r });
|
|
59
|
+
} catch (e) { return send(/no source/.test(e.message) ? 404 : 400, { error: e.message }); }
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
65
|
+
const a = process.argv.slice(2);
|
|
66
|
+
const opt = (n, d) => { const i = a.indexOf(`--${n}`); return i >= 0 ? a[i + 1] : d; };
|
|
67
|
+
const workspace = opt("workspace") ? resolve(opt("workspace")) : process.env.MENTURA_WORKSPACE;
|
|
68
|
+
const s = webhooks({ workspace });
|
|
69
|
+
s.listen(Number(opt("port", 4330)), opt("host", "127.0.0.1"), () => console.log(`webhooks on http://${opt("host", "127.0.0.1")}:${s.address().port}/webhooks/<venture>/<source> for ${workspace ?? "this repository's instance/"}`));
|
|
70
|
+
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
20
20
|
import { dirname, join } from "node:path";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { answersFor, configFor, questionsFor, threshold } from "../../../schema/naive-config.mjs";
|
|
22
23
|
|
|
23
24
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
24
25
|
const repo = join(here, "../../..");
|
|
@@ -163,8 +164,9 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
163
164
|
const opt = (n) => { const i = process.argv.indexOf(`--${n}`); return i >= 0 ? process.argv[i + 1] : undefined; };
|
|
164
165
|
const json = (x) => console.log(JSON.stringify(x, null, 2));
|
|
165
166
|
if (a === "split") json(split(readFileSync(b, "utf8")));
|
|
166
|
-
|
|
167
|
-
else if (a === "
|
|
167
|
+
// Gate group hook (P-2): the venture's naive.config.json says which System 1 questions run and the tag threshold.
|
|
168
|
+
else if (a === "request") json(questionsFor(request(split(readFileSync(join(b, DUMP), "utf8")), ventureOf(b)), configFor(b), "idea-map"));
|
|
169
|
+
else if (a === "answers") { const cfg = configFor(b); json(fromAnswers(answersFor(load(c).answers ?? load(c), cfg, "idea-map"), threshold(cfg, "idea-map", CONVENTIONS.threshold))); }
|
|
168
170
|
else if (a === "write") {
|
|
169
171
|
const ideas = split(readFileSync(join(b, DUMP), "utf8"));
|
|
170
172
|
mkdirSync(join(b, dirname(MAP)), { recursive: true });
|
|
@@ -25,6 +25,7 @@ import { CONVENTIONS, MAP, parse } from "./ideamap.mjs";
|
|
|
25
25
|
import { GRAPH, ORDER } from "./graph.mjs";
|
|
26
26
|
import { confirmedIn } from "../../../schema/foundation.mjs";
|
|
27
27
|
import { loadPackages } from "../../../schema/load-packages.mjs";
|
|
28
|
+
import { ventureFile } from "../../../schema/venture-paths.mjs";
|
|
28
29
|
|
|
29
30
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
31
|
const load = (p) => JSON.parse(readFileSync(p, "utf8"));
|
|
@@ -37,7 +38,7 @@ const HOME = { "venture-core": "foundation", brand: "marketing", ...Object.fromE
|
|
|
37
38
|
/** Understand is a gate (EE-069): the founder confirms Mentura read the ideas right, after any
|
|
38
39
|
* corrections, before they are routed. A person's confirm of the map after its last agent write. */
|
|
39
40
|
export function understood(dir) {
|
|
40
|
-
return existsSync(
|
|
41
|
+
return existsSync(ventureFile(dir, MAP)) && confirmedIn(dir, MAP, "ideas");
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export const STAGES = [["routed", "Routed"], ["drafted", "Drafted"], ["yours", "Your decision"], ["waiting", "Waiting"], ["done", "Done"]];
|
|
@@ -62,13 +63,13 @@ function written(dir, key) {
|
|
|
62
63
|
const rel = m?.paths?.[0]?.replace(/^instance\/\*\*\//, "");
|
|
63
64
|
if (!rel) return null;
|
|
64
65
|
if (rel.includes("*")) { const d = join(dir, dirname(rel)); return existsSync(d) && readdirSync(d).some((f) => f.endsWith(".json")); }
|
|
65
|
-
return existsSync(
|
|
66
|
+
return existsSync(ventureFile(dir, rel));
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export function route(dir, { now } = {}) {
|
|
69
70
|
const venture = load(join(dir, "venture.json"));
|
|
70
|
-
const g = existsSync(
|
|
71
|
-
const nodes = g?.nodes ?? (existsSync(
|
|
71
|
+
const g = existsSync(ventureFile(dir, GRAPH)) ? load(ventureFile(dir, GRAPH)) : null;
|
|
72
|
+
const nodes = g?.nodes ?? (existsSync(ventureFile(dir, MAP)) ? parse(readFileSync(ventureFile(dir, MAP), "utf8")).ideas.map((i) => ({ ...i, signal: null, lane: null })) : []);
|
|
72
73
|
if (!nodes.length) return { venture: venture.id, phase: venture.phase, waiting: "no idea map yet: map the idea dump first (purpose/ideas)" };
|
|
73
74
|
const lanes = new Map((g?.lanes ?? []).map((l) => [l.id, l]));
|
|
74
75
|
const m = decisions(dir, now);
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
21
21
|
import { dirname, join } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { ventureFile } from "../../../schema/venture-paths.mjs";
|
|
23
24
|
|
|
24
25
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
25
26
|
const ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
@@ -39,7 +40,7 @@ function readVenture(path) {
|
|
|
39
40
|
}
|
|
40
41
|
const venture = load(join(path, "venture.json"));
|
|
41
42
|
const dir = join(path, "products");
|
|
42
|
-
const cards = existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => load(
|
|
43
|
+
const cards = existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => load(ventureFile(dir, f))) : [];
|
|
43
44
|
const products = Object.fromEntries(cards.map((c) => [c.id, c]));
|
|
44
45
|
const flows = cards.flatMap((c) => (c.flows_to ?? []).map((to) => ({ from: c.id, to })));
|
|
45
46
|
return { venture, core: venture.core, products, flows };
|
package/schema/README.md
CHANGED
|
@@ -36,6 +36,7 @@ Status:
|
|
|
36
36
|
|---|---|---|---|
|
|
37
37
|
| Purpose | Core product | `core` in `venture.json` | defined |
|
|
38
38
|
| Purpose | Telos (start with the end) | `schema/telos.schema.json` at `<venture>/purpose/telos.json`; `check-telos.mjs` (EE-023) | defined |
|
|
39
|
+
| System | Venture config (`naive.config.json`: the gate's rules, owners, check settings, thresholds, System 1 questions, pinned packages, acting) | `schema/naive-config.schema.json` at `<venture>/naive.config.json`; `schema/naive-config.mjs` (defaults), `schema/check-config.mjs`, the gate `schema/gate.mjs` and held drafts `schema/gate-drafts.mjs` (EE-073) | defined |
|
|
39
40
|
| Governance | Publication policy (the publication gate) | `schema/publication-policy.schema.json` at `<venture>/governance/publication-policy.json`; `check-publication.mjs` (EE-025) | defined |
|
|
40
41
|
| Purpose | Validation package: problem record, shared evidence item, GRADE-adapted grading | `schema/validation-problem.schema.json` and `schema/validation-evidence.schema.json` at `<venture>/purpose/validation/`; `check-validation.mjs`; package at `packages/validation/` (templates and skills still under `purpose/kits/validation/`, EE-026, EE-071) | problem built; six sub-scores to build |
|
|
41
42
|
| Purpose | Goals and vision | `purpose/frameworks/business-discovery-framework.md` | prose |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Access (P-27, the robots of a venture): what each team-plan role may read and write, as globs over
|
|
2
|
+
// routes (purpose/**, product/*). The same rules the router applies before every action, here so the
|
|
3
|
+
// gate (X-4) and every read of a record (F-5) can check them again. Files only: no model, no network.
|
|
4
|
+
//
|
|
5
|
+
// A role the team plan does not list, or one that lists no routes, may reach every route; gates
|
|
6
|
+
// still apply. A role with routes may read every route unless it lists read, and write none unless
|
|
7
|
+
// it lists write.
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
12
|
+
|
|
13
|
+
export const PLANS = ["people/team-plan.json", "operations/team-plan.json"];
|
|
14
|
+
|
|
15
|
+
// Globs over routes, as in the team plan: * is one segment, ** any depth.
|
|
16
|
+
export const routeGlob = (g) => new RegExp("^" + g.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*\*/g, "\u0001").replace(/\*/g, "[^/]*").replace(/\0/g, "(?:[^/]+/)*").replace(/\u0001/g, ".*") + "$");
|
|
17
|
+
|
|
18
|
+
/** The venture's team plan, or null. Read fresh on every call, so a changed plan is seen at once. */
|
|
19
|
+
export function teamPlan(dir) {
|
|
20
|
+
const f = dir && PLANS.map((p) => ventureFile(dir, p)).find((p) => existsSync(p));
|
|
21
|
+
return f ? { path: f, ...JSON.parse(readFileSync(f, "utf8")) } : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A role's entry in the team plan, or null. */
|
|
25
|
+
export function roleOf(dir, roleId) {
|
|
26
|
+
if (!roleId) return null;
|
|
27
|
+
return teamPlan(dir)?.roles?.find((r) => r.id === roleId) ?? null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The globs a role may read and write, or null for a role that may reach every route. */
|
|
31
|
+
export function globsOf(dir, roleId) {
|
|
32
|
+
const role = roleOf(dir, roleId);
|
|
33
|
+
if (!role?.routes) return null;
|
|
34
|
+
return { read: role.routes.read ?? ["**"], write: role.routes.write ?? [] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Whether a role may read or write a route. */
|
|
38
|
+
export function may(dir, roleId, act, route) {
|
|
39
|
+
if (!roleId || !dir) return true;
|
|
40
|
+
const g = globsOf(dir, roleId);
|
|
41
|
+
if (!g) return true;
|
|
42
|
+
return (act === "write" ? g.write : g.read).some((x) => routeGlob(x).test(route));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether a role may read at least one item of a collection: it may read the list route itself,
|
|
47
|
+
* or one of its read globs reaches an item under it (product/kettle under product).
|
|
48
|
+
*/
|
|
49
|
+
export function mayReadSome(dir, roleId, listRoute) {
|
|
50
|
+
if (may(dir, roleId, "read", listRoute)) return true;
|
|
51
|
+
const g = globsOf(dir, roleId);
|
|
52
|
+
return !!g && g.read.some((x) => x.startsWith(`${listRoute}/`) || routeGlob(x).test(`${listRoute}/x`));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Whether a role may read one item of a collection: the list route covers its items, or the item route is listed. */
|
|
56
|
+
export function mayReadItem(dir, roleId, listRoute, id) {
|
|
57
|
+
return may(dir, roleId, "read", listRoute) || may(dir, roleId, "read", `${listRoute}/${id}`);
|
|
58
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "adapter.schema.json",
|
|
4
|
+
"title": "Adapter",
|
|
5
|
+
"description": "What runs Naive on a platform or in an agent harness (Z-4), as a Next.js adapter customizes build and deploy for a platform. A harness adapter (Claude Code, Codex, Cursor) adapts the framework through the venture's AGENTS.md and skills: the entry files it writes into a venture, and where it puts the package skills. A platform adapter (Mentura, Naive's first, as Vercel is for Next.js) names the tests it runs. An adapter is verified when it passes the framework's tests (node interface/adapters.mjs verify <name>). Harness adapters are interface/adapters/<name>.json; the platform's is platform/adapter.json.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["name", "title", "kind", "reads", "files", "skills", "sources"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"name": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
11
|
+
"title": { "type": "string" },
|
|
12
|
+
"kind": { "enum": ["harness", "platform"] },
|
|
13
|
+
"integration": { "type": "string", "description": "Its entry in interface/tooling/integrations.json" },
|
|
14
|
+
"reads": { "type": "array", "items": { "type": "string" }, "description": "The files the harness or platform reads on its own, as globs" },
|
|
15
|
+
"files": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"description": "Entry files the adapter writes into a venture; each must lead to the venture's AGENTS.md",
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"required": ["path", "text"],
|
|
21
|
+
"additionalProperties": false,
|
|
22
|
+
"properties": {
|
|
23
|
+
"path": { "type": "string", "pattern": "^(?!/)(?!.*\\.\\.)(?!_private/).+$" },
|
|
24
|
+
"text": { "type": "string" }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"skills": { "type": ["string", "null"], "description": "The folder the harness loads skills from; each open package's skill is copied to <folder>/<skill id>/SKILL.md. null when the harness reaches skills through AGENTS.md" },
|
|
29
|
+
"verify": { "type": "array", "items": { "type": "string" }, "description": "A platform adapter: the test programs it must pass, each run as node <path> and ending with all passing or exit 0" },
|
|
30
|
+
"sources": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "Where the harness documents what it reads" },
|
|
31
|
+
"note": { "type": "string" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { catalog, foldersOf } from "./departments.mjs";
|
|
14
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
14
15
|
|
|
15
16
|
const args = process.argv.slice(2);
|
|
16
17
|
const group = args.includes("--group");
|
|
@@ -28,8 +29,8 @@ const tag = { department: d.id, value_chain: d.value_chain, apqc: d.apqc.id };
|
|
|
28
29
|
const made = [];
|
|
29
30
|
for (const layer of layersOn.filter((l) => !held.includes(l))) {
|
|
30
31
|
const rel = join(layer, "departments", ...(group ? [d.value_chain] : []), d.id);
|
|
31
|
-
if (existsSync(
|
|
32
|
-
mkdirSync(
|
|
32
|
+
if (existsSync(ventureFile(dir, rel))) { console.error(`${rel} exists without a department.json; add the tag by hand`); process.exit(1); }
|
|
33
|
+
mkdirSync(ventureFile(dir, rel), { recursive: true });
|
|
33
34
|
writeFileSync(join(dir, rel, "department.json"), JSON.stringify(tag, null, 2) + "\n");
|
|
34
35
|
const [file, title, what] = layer === "governance"
|
|
35
36
|
? ["charter.md", "charter", d.governance] : ["README.md", "operations", d.operations];
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import { existsSync, readFileSync } from "node:fs";
|
|
28
28
|
import { join } from "node:path";
|
|
29
|
+
import { ventureFile } from "./venture-paths.mjs";
|
|
29
30
|
|
|
30
31
|
export const ASSUMPTIONS = [
|
|
31
32
|
{ id: "exists", says: "the problem is real", field: "statement", test: "Find people already paying for or building a workaround (search, forums, competitors' customers)" },
|
|
@@ -97,7 +98,7 @@ export const ASK = "Some things the assessment reads are missing. Fill them in f
|
|
|
97
98
|
|
|
98
99
|
/** The whole run: preflight first, then the assessment unless input is missing and as_is is not set. */
|
|
99
100
|
export function assess(dir, { as_is = false, today = new Date() } = {}) {
|
|
100
|
-
const f = join(dir, "purpose/validation/problem.json");
|
|
101
|
+
const f = join(ventureFile(dir, "purpose/validation/problem.json"));
|
|
101
102
|
const rec = existsSync(f) ? JSON.parse(readFileSync(f, "utf8")) : null;
|
|
102
103
|
const missing = preflight(rec);
|
|
103
104
|
if (missing.length && !as_is) return { needs_input: { ask: ASK, missing, options: [
|
|
@@ -117,7 +118,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
117
118
|
const dir = process.argv[2];
|
|
118
119
|
if (!dir) { console.error("usage: node schema/assess-problem.mjs <venture dir> [--json] [--preflight] [--as-is]"); process.exit(2); }
|
|
119
120
|
const flag = (f) => process.argv.includes(f);
|
|
120
|
-
const out = flag("--preflight") ? { missing: preflight(existsSync(join(dir, "purpose/validation/problem.json")) ? JSON.parse(readFileSync(join(dir, "purpose/validation/problem.json"), "utf8")) : null) } : assess(dir, { as_is: flag("--as-is") });
|
|
121
|
+
const out = flag("--preflight") ? { missing: preflight(existsSync(join(ventureFile(dir, "purpose/validation/problem.json"))) ? JSON.parse(readFileSync(join(ventureFile(dir, "purpose/validation/problem.json")), "utf8")) : null) } : assess(dir, { as_is: flag("--as-is") });
|
|
121
122
|
if (flag("--json")) { console.log(JSON.stringify(out, null, 2)); process.exit(0); }
|
|
122
123
|
const list = (m) => m.map((x) => ` - ${x.field}${x.items ? ` (${x.items.join(", ")})` : ""}: ${x.why}. As is: ${x.as_is}`).join("\n");
|
|
123
124
|
if (out.needs_input) { console.log(`${out.needs_input.ask}\n${list(out.needs_input.missing)}\nFill in: ${out.needs_input.options[0].how}\nRun as is: ${out.needs_input.options[1].how}`); process.exit(0); }
|