@usegraft/cli 0.1.1 → 1.0.0-beta.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/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # @usegraft/cli
2
+
3
+ > The `graft` command. Scaffold a project, compile authored content, serve the runtime, split your database credentials.
4
+
5
+ Part of [Graft](https://github.com/AndersonDesign1/graft), a CMS built so an AI agent is the primary operator.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i -D @usegraft/cli
11
+ # or run it without installing
12
+ npx @usegraft/cli init
13
+ ```
14
+
15
+ Once installed, the command is `graft`. Run it without installing under the
16
+ full package name: the bare name `graft` on npm belongs to an unrelated
17
+ package, so `npx graft` fetches the wrong thing.
18
+
19
+ ## Quickstart, with no database
20
+
21
+ A content project needs no services at all. `graft compile` writes the index to a SQLite artifact your app reads embedded.
22
+
23
+ ```bash
24
+ npx @usegraft/cli init # scaffolds graft.config.ts, content/, llms.txt
25
+ npx @usegraft/cli compile # → .graft/index.db
26
+ ```
27
+
28
+ Move to Postgres by setting `DATABASE_URL` and changing one line in `graft.config.ts`:
29
+
30
+ ```ts
31
+ export const index = "postgres";
32
+ ```
33
+
34
+ ## Commands
35
+
36
+ | Command | Does |
37
+ | ------------------------------ | ---------------------------------------------------------------------------------------------- |
38
+ | `graft init` | Scaffold a project |
39
+ | `graft compile` | Validate authored MDX and project it into the index |
40
+ | `graft dev` | Recompile on save |
41
+ | `graft serve` | Run the headless runtime: typed functions, MCP, authored-content reads, `/healthz` |
42
+ | `graft studio` | Open the optional editing UI |
43
+ | `graft mcp` | Serve MCP on stdio, for `.mcp.json` |
44
+ | `graft add <item>` | Copy a registry primitive into your repo, shadcn-style |
45
+ | `graft approve <id>` | Decide a pending approval. Operator credential only |
46
+ | `graft harden <role>` | Grant a Postgres role exactly what a deployment needs, and nothing that can decide an approval |
47
+ | `graft branch` / `graft merge` | Copy-on-write content branches |
48
+ | `graft migrate` | Run content codemods when the schema changes |
49
+
50
+ Run `graft <command> --help` for flags.
51
+
52
+ ## Two credentials
53
+
54
+ `graft harden` splits the operator credential from the runtime one. The runtime can serve, project content, and request approvals. It can never decide one, and Postgres enforces that rather than the application.
55
+
56
+ ```bash
57
+ graft harden graft_runtime
58
+ # deploy with the runtime role's URL as DATABASE_URL
59
+ # keep the operator URL for compile, migrate, merge and approve
60
+ ```
61
+
62
+ ---
63
+
64
+ MIT. [Repository](https://github.com/AndersonDesign1/graft) · [Changelog](https://github.com/AndersonDesign1/graft/blob/main/packages/cli/CHANGELOG.md) · [Security policy](https://github.com/AndersonDesign1/graft/blob/main/SECURITY.md)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  findConfig
4
- } from "./chunk-BUPFXTUG.js";
4
+ } from "./chunk-2LG6TWYY.js";
5
5
 
6
6
  // src/commands/add.ts
7
7
  import { dirname } from "path";
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ approvalsListCommand,
4
+ decideCommand,
5
+ formatApproval,
6
+ operatorName
7
+ } from "./chunk-NBJ4N4O6.js";
8
+ import "./chunk-2LG6TWYY.js";
9
+ export {
10
+ approvalsListCommand,
11
+ decideCommand,
12
+ formatApproval,
13
+ operatorName
14
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadProjectEnv
4
- } from "./chunk-BUPFXTUG.js";
4
+ } from "./chunk-2LG6TWYY.js";
5
5
 
6
6
  // src/commands/asset.ts
7
7
  import { readFileSync, statSync } from "fs";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadProjectEnv,
4
4
  requireDatabaseUrl
5
- } from "./chunk-BUPFXTUG.js";
5
+ } from "./chunk-2LG6TWYY.js";
6
6
 
7
7
  // src/commands/branch.ts
8
8
  import { GraftError } from "@usegraft/contracts";
@@ -3,7 +3,7 @@
3
3
  // src/config.ts
4
4
  import { existsSync } from "fs";
5
5
  import { dirname, join, resolve } from "path";
6
- import { GraftError } from "@usegraft/contracts";
6
+ import { GraftError, STATIC_INDEX_DEFAULT_PATH } from "@usegraft/contracts";
7
7
  import { createJiti } from "jiti";
8
8
  var CONFIG_FILENAMES = [
9
9
  "graft.config.ts",
@@ -97,6 +97,8 @@ async function loadConfig(configPath) {
97
97
  const projectDir = dirname(configPath);
98
98
  const index = parseIndexConfig(mod.index, projectDir, configPath);
99
99
  assertStaticSupports(index, collections, functions, configPath);
100
+ const mdxTrust = parseMdxTrust(mod.mdxTrust, configPath);
101
+ const approvalPolicy = parseApprovalPolicy(mod.approvalPolicy, configPath);
100
102
  const contentDirSetting = typeof mod.contentDir === "string" ? mod.contentDir : "content";
101
103
  const migrationsDirSetting = typeof mod.migrationsDir === "string" ? mod.migrationsDir : "migrations";
102
104
  return {
@@ -106,9 +108,31 @@ async function loadConfig(configPath) {
106
108
  migrationsDir: resolve(projectDir, migrationsDirSetting),
107
109
  index,
108
110
  collections,
109
- functions
111
+ functions,
112
+ mdxTrust,
113
+ approvalPolicy
110
114
  };
111
115
  }
116
+ function parseMdxTrust(value, configPath) {
117
+ if (value === void 0) return "restricted";
118
+ if (value === "restricted" || value === "full") return value;
119
+ throw new GraftError({
120
+ code: "CONFIG_INVALID",
121
+ message: `${configPath} exports \`mdxTrust\` as ${JSON.stringify(value)}, which is not "restricted" or "full".`,
122
+ fix: 'Export `mdxTrust = "restricted"` (the default: `{\u2026}` expressions and `import` are refused in authored bodies) or `mdxTrust = "full"` (every author has commit access, so code review is the control). Omit the export to keep the default.',
123
+ details: { mdxTrust: value }
124
+ });
125
+ }
126
+ function parseApprovalPolicy(value, configPath) {
127
+ if (value === void 0) return "none";
128
+ if (value === "none" || value === "human" || value === "unattended") return value;
129
+ throw new GraftError({
130
+ code: "CONFIG_INVALID",
131
+ message: `${configPath} exports \`approvalPolicy\` as ${JSON.stringify(value)}, which is not "none", "human" or "unattended".`,
132
+ fix: 'Export `approvalPolicy = "none"` (the default: only `destructive` functions are gated), `"human"` (every mutation is gated), or `"unattended"` (nothing is gated, for a caller with no human behind it). Omit the export to keep the default.',
133
+ details: { approvalPolicy: value }
134
+ });
135
+ }
112
136
  function assertStaticSupports(index, collections, functions, configPath) {
113
137
  if (index.driver !== "static") return;
114
138
  const dbCollections = Object.values(collections).filter((collection) => collection.authority === "db-authoritative").map((collection) => collection.name);
@@ -125,18 +149,17 @@ function assertStaticSupports(index, collections, functions, configPath) {
125
149
  details: { dbCollections, functions: functionNames }
126
150
  });
127
151
  }
128
- var STATIC_INDEX_DEFAULT = ".graft/index.db";
129
152
  function parseIndexConfig(value, projectDir, configPath) {
130
153
  if (value === void 0 || value === "postgres") return { driver: "postgres" };
131
154
  if (value === "static") {
132
- return { driver: "static", path: resolve(projectDir, STATIC_INDEX_DEFAULT) };
155
+ return { driver: "static", path: resolve(projectDir, STATIC_INDEX_DEFAULT_PATH) };
133
156
  }
134
157
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
135
158
  const driver = value.driver;
136
159
  const path = value.path;
137
160
  if (driver === "postgres") return { driver: "postgres" };
138
161
  if (driver === "static" && (path === void 0 || typeof path === "string")) {
139
- return { driver: "static", path: resolve(projectDir, path ?? STATIC_INDEX_DEFAULT) };
162
+ return { driver: "static", path: resolve(projectDir, path ?? STATIC_INDEX_DEFAULT_PATH) };
140
163
  }
141
164
  }
142
165
  throw new GraftError({
@@ -7,7 +7,7 @@ import {
7
7
  loadConfig,
8
8
  loadProjectEnv,
9
9
  requireDatabaseUrl
10
- } from "./chunk-BUPFXTUG.js";
10
+ } from "./chunk-2LG6TWYY.js";
11
11
 
12
12
  // src/commands/compile.ts
13
13
  import { GraftError } from "@usegraft/contracts";
@@ -29,6 +29,7 @@ async function compileCommand(options) {
29
29
  const result = await compileStatic({
30
30
  contentDir: config.contentDir,
31
31
  collections: config.collections,
32
+ mdxTrust: config.mdxTrust,
32
33
  indexPath: config.index.path
33
34
  });
34
35
  console.log(formatCompileResult(result));
@@ -52,6 +53,7 @@ async function compileCommand(options) {
52
53
  db: branch.db,
53
54
  contentDir: config.contentDir,
54
55
  collections: config.collections,
56
+ mdxTrust: config.mdxTrust,
55
57
  branchId: scopeWriteBranch(branch.scope),
56
58
  pruneUnknown: options.pruneUnknown
57
59
  });
@@ -4,19 +4,23 @@ import {
4
4
  loadConfig,
5
5
  loadProjectEnv,
6
6
  requireDatabaseUrl
7
- } from "./chunk-BUPFXTUG.js";
7
+ } from "./chunk-2LG6TWYY.js";
8
8
 
9
9
  // src/commands/serve.ts
10
10
  import { execFileSync } from "child_process";
11
11
  import { createServer } from "http";
12
12
  import { createActorResolver } from "@usegraft/auth";
13
13
  import { GraftError } from "@usegraft/contracts";
14
+ import { setRequestPeer } from "@usegraft/core";
14
15
  function createServeRouter(routes) {
15
16
  return async (request) => {
16
17
  const { pathname } = new URL(request.url);
17
18
  if (pathname === "/healthz") return routes.health(request);
18
19
  if (pathname === "/api/mcp") return routes.mcp(request);
19
20
  if (pathname === "/api/fn" || pathname.startsWith("/api/fn/")) return routes.fn(request);
21
+ if (pathname === "/api/content/v1" || pathname.startsWith("/api/content/v1/")) {
22
+ return routes.content(request);
23
+ }
20
24
  if (routes.studio && (pathname === "/studio" || pathname.startsWith("/studio/") || pathname.startsWith("/api/studio/"))) {
21
25
  return routes.studio(request);
22
26
  }
@@ -24,7 +28,7 @@ function createServeRouter(routes) {
24
28
  const error = new GraftError({
25
29
  code: "ROUTE_NOT_FOUND",
26
30
  message: `Nothing is mounted at ${pathname}.`,
27
- fix: `Use POST /api/fn/<name> (typed functions), POST /api/mcp (MCP Streamable HTTP), or GET /healthz${studioHint}.`,
31
+ fix: `Use POST /api/fn/<name> (typed functions), POST /api/mcp (MCP Streamable HTTP), GET /api/content/v1/documents (authored content), GET /api/content/v1/search, or GET /healthz${studioHint}.`,
28
32
  details: { pathname }
29
33
  });
30
34
  return new Response(JSON.stringify(error.toJSON()), {
@@ -41,9 +45,34 @@ var HOP_BY_HOP = /* @__PURE__ */ new Set([
41
45
  "upgrade",
42
46
  "proxy-connection"
43
47
  ]);
44
- function createNodeListener(handler) {
48
+ function allowedHostsFor(host) {
49
+ if (host === "0.0.0.0" || host === "::" || host === "") return void 0;
50
+ if (host === "127.0.0.1" || host === "localhost" || host === "::1") {
51
+ return ["127.0.0.1", "localhost", "[::1]"];
52
+ }
53
+ return [host];
54
+ }
55
+ function hostname(host) {
56
+ const trimmed = host.trim().toLowerCase();
57
+ if (trimmed.startsWith("[")) return trimmed.slice(0, trimmed.indexOf("]") + 1);
58
+ const colon = trimmed.lastIndexOf(":");
59
+ return colon === -1 ? trimmed : trimmed.slice(0, colon);
60
+ }
61
+ function createNodeListener(handler, options = {}) {
62
+ const allowed = options.allowedHosts?.map(hostname);
45
63
  return async (req, res) => {
46
64
  try {
65
+ if (allowed && !allowed.includes(hostname(req.headers.host ?? ""))) {
66
+ res.writeHead(400, { "content-type": "application/json" });
67
+ res.end(
68
+ JSON.stringify({
69
+ error: "INPUT_VALIDATION_FAILED",
70
+ message: `This server does not answer to Host "${req.headers.host ?? ""}".`,
71
+ fix: `Reach it at one of: ${allowed.join(", ")}. A mismatched Host is either a misconfigured proxy or a DNS-rebinding attempt.`
72
+ })
73
+ );
74
+ return;
75
+ }
47
76
  const chunks = [];
48
77
  for await (const chunk of req) chunks.push(chunk);
49
78
  const body = Buffer.concat(chunks);
@@ -59,6 +88,8 @@ function createNodeListener(handler) {
59
88
  headers,
60
89
  body: method === "GET" || method === "HEAD" || body.length === 0 ? void 0 : body
61
90
  });
91
+ const peer = req.socket.remoteAddress;
92
+ if (peer) setRequestPeer(request, peer);
62
93
  const response = await handler(request);
63
94
  const outHeaders = {};
64
95
  response.headers.forEach((value, key) => {
@@ -100,11 +131,13 @@ async function startServe(options) {
100
131
  const [
101
132
  { createFunctionsHandler },
102
133
  { createGraftMcpHandler },
103
- { createDb, resolveBranchHandle, scopeWriteBranch, sql },
134
+ { createContentApiHandler },
135
+ { createDb, createDbIndexReader, resolveBranchHandle, scopeWriteBranch, sql },
104
136
  studioMod
105
137
  ] = await Promise.all([
106
138
  import("@usegraft/core"),
107
139
  import("@usegraft/mcp"),
140
+ import("@usegraft/content-api"),
108
141
  import("@usegraft/db"),
109
142
  enableStudio ? import("@usegraft/studio") : Promise.resolve(null)
110
143
  ]);
@@ -119,8 +152,16 @@ async function startServe(options) {
119
152
  issuers,
120
153
  devTokens: devToken ? { [devToken]: { kind: "agent", id: "graft-serve", scopes } } : void 0
121
154
  });
122
- const approvalPolicy = process.env.GRAFT_APPROVAL_POLICY === "human" ? "human" : "none";
123
- const requireMcpActor = process.env.GRAFT_MCP_REQUIRE_AUTH === "1";
155
+ const approvalPolicy = config.approvalPolicy;
156
+ if (process.env.GRAFT_APPROVAL_POLICY) {
157
+ console.warn(
158
+ `[graft serve] GRAFT_APPROVAL_POLICY is set and is now ignored. Approval policy moved to graft.config.ts so that turning off the gate on destructive functions appears in a diff. Export \`approvalPolicy = "${process.env.GRAFT_APPROVAL_POLICY}"\` there instead. Serving with "${approvalPolicy}".`
159
+ );
160
+ }
161
+ const host = options.host ?? process.env.HOST ?? "127.0.0.1";
162
+ const loopback = host === "127.0.0.1" || host === "localhost" || host === "::1";
163
+ const anonymousOptIn = process.env.GRAFT_MCP_ALLOW_ANONYMOUS === "1";
164
+ const allowAnonymousMcp = loopback || anonymousOptIn;
124
165
  const functions = config.functions ?? {};
125
166
  const gitSha = localGitSha(options.cwd);
126
167
  const fnHandler = createFunctionsHandler({
@@ -138,11 +179,31 @@ async function startServe(options) {
138
179
  contentDir: config.contentDir,
139
180
  collections: config.collections,
140
181
  functions,
182
+ mdxTrust: config.mdxTrust,
141
183
  db: branch.db,
142
184
  branchId: writeBranch,
143
185
  scope: branch.scope,
144
186
  actor: resolveActor,
145
- requireActor: requireMcpActor
187
+ allowAnonymous: allowAnonymousMcp,
188
+ // The same backstop the functions handler gets. Without it a function with
189
+ // no per-function rateLimit was capped over POST /api/fn and uncapped over
190
+ // run_function, so the transport decided the limit — and tools/functions.ts
191
+ // claims the two surfaces apply rate limits identically.
192
+ rateLimit: { limit: 60, windowSeconds: 60 }
193
+ });
194
+ const allowedOriginsRaw = process.env.GRAFT_CONTENT_ALLOWED_ORIGINS?.trim();
195
+ const allowedOrigins = allowedOriginsRaw === void 0 || allowedOriginsRaw === "" ? void 0 : allowedOriginsRaw === "*" ? "*" : allowedOriginsRaw.split(",").map((entry) => entry.trim()).filter(Boolean);
196
+ const contentHandler = createContentApiHandler({
197
+ collections: Object.keys(config.collections),
198
+ branch: writeBranch,
199
+ index: createDbIndexReader(branch.db),
200
+ ...allowedOrigins === void 0 ? {} : { allowedOrigins },
201
+ // The same backstop the functions handler gets, for the same reason. These
202
+ // routes authenticate nobody and run database listings and full-text
203
+ // searches, so without a limit here one anonymous caller can keep the index
204
+ // busy indefinitely — and `graft serve` is the mount most likely to face
205
+ // the open internet.
206
+ rateLimit: { limit: 60, windowSeconds: 60 }
146
207
  });
147
208
  const health = async () => {
148
209
  try {
@@ -167,7 +228,6 @@ async function startServe(options) {
167
228
  { status: 200, headers: { "content-type": "application/json" } }
168
229
  );
169
230
  };
170
- const host = options.host ?? process.env.HOST ?? "127.0.0.1";
171
231
  const requestedPort = options.port ?? Number(process.env.PORT ?? 3903);
172
232
  if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65535) {
173
233
  await branch.close();
@@ -178,31 +238,50 @@ async function startServe(options) {
178
238
  fix: "Pass --port <0-65535> (0 picks a free port) or set PORT."
179
239
  });
180
240
  }
181
- const loopback = host === "127.0.0.1" || host === "localhost" || host === "::1";
182
- if (!loopback && !requireMcpActor && issuers.length === 0 && !devToken) {
241
+ if (!loopback && anonymousOptIn) {
242
+ console.warn(
243
+ "[graft serve] WARNING: GRAFT_MCP_ALLOW_ANONYMOUS=1 while bound beyond loopback \u2014 unauthenticated callers can write content, upload assets and decide approvals. Only do this behind an auth proxy you control."
244
+ );
245
+ } else if (!loopback && issuers.length === 0 && !devToken) {
183
246
  console.warn(
184
- "[graft serve] WARNING: binding beyond loopback with no identity configured \u2014 set GRAFT_MCP_REQUIRE_AUTH=1 and provide GRAFT_DEV_TOKEN or GRAFT_TRUSTED_ISSUERS before exposing this to a network."
247
+ "[graft serve] WARNING: binding beyond loopback with no identity configured \u2014 MCP will refuse every caller, because there is nothing to authenticate them against. Set GRAFT_DEV_TOKEN or GRAFT_TRUSTED_ISSUERS."
248
+ );
249
+ }
250
+ if (approvalPolicy === "unattended") {
251
+ console.warn(
252
+ '[graft serve] WARNING: approvalPolicy = "unattended" in graft.config.ts \u2014 destructive functions run without human approval. Deletes are not recoverable from git: deleteRecord removes rows outright and the asset store keeps no history. Audit rows are still written. This applies to POST /api/fn only; run_function over /api/mcp stays gated, because the policy is not part of the MCP surface.'
185
253
  );
186
254
  }
187
255
  let studioHandler;
188
256
  if (studioMod) {
189
- const authorize = !loopback && (devToken || issuers.length > 0) ? async (request) => {
257
+ const authenticate = !loopback && (devToken || issuers.length > 0) ? async (request) => {
190
258
  const actor = await resolveActor(request);
191
- return actor.kind !== "anonymous";
192
- } : !loopback ? () => false : void 0;
259
+ if (actor.kind === "anonymous" || !actor.id) return null;
260
+ return { kind: actor.kind, id: actor.id, scopes: actor.scopes ?? [] };
261
+ } : !loopback ? () => null : void 0;
193
262
  studioHandler = studioMod.createStudioHandler({
194
263
  db: branch.db,
195
264
  collections: config.collections,
196
265
  contentDir: config.contentDir,
266
+ mdxTrust: config.mdxTrust,
197
267
  defaultBranch: writeBranch,
198
- decidedBy: "studio-serve",
268
+ // Only reached on a loopback mount, where there is no caller identity to
269
+ // attribute to. Off loopback the authenticated principal is used instead.
270
+ decider: { kind: "agent", id: "studio-serve" },
199
271
  uiBasePath: "/studio",
200
- authorize
272
+ authenticate
201
273
  });
202
274
  }
203
275
  const server = createServer(
204
276
  createNodeListener(
205
- createServeRouter({ fn: fnHandler, mcp: mcpHandler, health, studio: studioHandler })
277
+ createServeRouter({
278
+ fn: fnHandler,
279
+ mcp: mcpHandler,
280
+ content: contentHandler,
281
+ health,
282
+ studio: studioHandler
283
+ }),
284
+ { allowedHosts: allowedHostsFor(host) }
206
285
  )
207
286
  );
208
287
  await new Promise((resolve, reject) => {
@@ -233,6 +312,8 @@ async function serveCommand(options) {
233
312
  `graft serve \u2014 branch "${running.branch}"`,
234
313
  ` functions POST ${base}/api/fn/<name>`,
235
314
  ` mcp POST ${base}/api/mcp`,
315
+ ` documents GET ${base}/api/content/v1/documents`,
316
+ ` search GET ${base}/api/content/v1/search`,
236
317
  ` health GET ${base}/healthz`,
237
318
  ...studioOn ? [
238
319
  ` studio GET ${base}/studio`,
@@ -250,6 +331,7 @@ async function serveCommand(options) {
250
331
 
251
332
  export {
252
333
  createServeRouter,
334
+ allowedHostsFor,
253
335
  createNodeListener,
254
336
  startServe,
255
337
  serveCommand
@@ -4,7 +4,7 @@ import {
4
4
  loadConfig,
5
5
  loadProjectEnv,
6
6
  requireDatabaseUrl
7
- } from "./chunk-BUPFXTUG.js";
7
+ } from "./chunk-2LG6TWYY.js";
8
8
 
9
9
  // src/commands/migrate.ts
10
10
  import { existsSync, readdirSync, statSync } from "fs";
@@ -99,6 +99,7 @@ Author one as migrations/0001-<name>.ts default-exporting defineContentMigration
99
99
  db: branch.db,
100
100
  contentDir: config.contentDir,
101
101
  collections: config.collections,
102
+ mdxTrust: config.mdxTrust,
102
103
  branchId: writeBranch,
103
104
  gitSha
104
105
  });
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadProjectEnv,
4
4
  requireDatabaseUrl
5
- } from "./chunk-BUPFXTUG.js";
5
+ } from "./chunk-2LG6TWYY.js";
6
6
 
7
7
  // src/commands/approvals.ts
8
8
  import { userInfo } from "os";
@@ -33,12 +33,10 @@ async function decideCommand(options) {
33
33
  ]);
34
34
  const handle = createDb(url);
35
35
  try {
36
- const row = await decideApproval(
37
- handle.db,
38
- options.id,
39
- options.decision,
40
- options.decidedBy ?? operatorName()
41
- );
36
+ const row = await decideApproval(handle.db, options.id, options.decision, {
37
+ kind: "human",
38
+ id: operatorName()
39
+ });
42
40
  if (!row) {
43
41
  throw new GraftError({
44
42
  code: "APPROVAL_INVALID",
@@ -61,7 +59,9 @@ function formatApproval(row) {
61
59
  ` input: ${JSON.stringify(row.input)}`
62
60
  ].join("\n");
63
61
  }
62
+
64
63
  export {
64
+ operatorName,
65
65
  approvalsListCommand,
66
66
  decideCommand,
67
67
  formatApproval
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadProjectEnv,
4
4
  requireDatabaseUrl
5
- } from "./chunk-BUPFXTUG.js";
5
+ } from "./chunk-2LG6TWYY.js";
6
6
 
7
7
  // src/commands/compilations.ts
8
8
  async function compilationsListCommand(options) {
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  assertNoStaticBranch,
4
4
  compileCommand
5
- } from "./chunk-KZ3FDWUK.js";
5
+ } from "./chunk-2N3HXYNN.js";
6
6
  import "./chunk-GPA66AP5.js";
7
- import "./chunk-BUPFXTUG.js";
7
+ import "./chunk-2LG6TWYY.js";
8
8
  export {
9
9
  assertNoStaticBranch,
10
10
  compileCommand
@@ -4,7 +4,7 @@ import {
4
4
  loadConfig,
5
5
  loadProjectEnv,
6
6
  requireDatabaseUrl
7
- } from "./chunk-BUPFXTUG.js";
7
+ } from "./chunk-2LG6TWYY.js";
8
8
 
9
9
  // src/commands/content.ts
10
10
  async function contentListCommand(options) {
@@ -4,7 +4,7 @@ import {
4
4
  loadConfig,
5
5
  loadProjectEnv,
6
6
  requireDatabaseUrl
7
- } from "./chunk-BUPFXTUG.js";
7
+ } from "./chunk-2LG6TWYY.js";
8
8
 
9
9
  // src/commands/db.ts
10
10
  import { GraftError } from "@usegraft/contracts";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  assertNoStaticBranch
4
- } from "./chunk-KZ3FDWUK.js";
4
+ } from "./chunk-2N3HXYNN.js";
5
5
  import {
6
6
  formatCompileResult,
7
7
  printGraftError
@@ -12,7 +12,7 @@ import {
12
12
  loadConfig,
13
13
  loadProjectEnv,
14
14
  requireDatabaseUrl
15
- } from "./chunk-BUPFXTUG.js";
15
+ } from "./chunk-2LG6TWYY.js";
16
16
 
17
17
  // src/commands/dev.ts
18
18
  import { existsSync, watch } from "fs";
@@ -42,6 +42,7 @@ async function devCommand(options) {
42
42
  const branch = control !== void 0 && url !== void 0 ? await resolveBranchHandle(control.db, options.branchId ?? "main", { databaseUrl: url }) : void 0;
43
43
  const writeBranch = branch !== void 0 ? scopeWriteBranch(branch.scope) : "main";
44
44
  let timer;
45
+ let rewatchContent;
45
46
  let compiling = false;
46
47
  let dirty = false;
47
48
  let configDirty = false;
@@ -54,17 +55,21 @@ async function devCommand(options) {
54
55
  try {
55
56
  if (configDirty) {
56
57
  configDirty = false;
58
+ const previousContentDir = config.contentDir;
57
59
  config = await loadConfig(configPath);
58
60
  console.log(`reloaded ${basename(configPath)}`);
61
+ if (config.contentDir !== previousContentDir) rewatchContent?.(config.contentDir);
59
62
  }
60
63
  const result = branch !== void 0 ? await compile({
61
64
  db: branch.db,
62
65
  contentDir: config.contentDir,
63
66
  collections: config.collections,
67
+ mdxTrust: config.mdxTrust,
64
68
  branchId: writeBranch
65
69
  }) : await compileStatic({
66
70
  contentDir: config.contentDir,
67
71
  collections: config.collections,
72
+ mdxTrust: config.mdxTrust,
68
73
  indexPath: config.index.path
69
74
  });
70
75
  console.log(formatCompileResult(result));
@@ -85,12 +90,17 @@ async function devCommand(options) {
85
90
  timer = setTimeout(() => void runCompile(), DEBOUNCE_MS);
86
91
  };
87
92
  await runCompile();
93
+ let contentWatcher = watch(config.contentDir, { recursive: true }, () => schedule());
88
94
  const watchers = [
89
- watch(config.contentDir, { recursive: true }, () => schedule()),
90
95
  watch(config.projectDir, (_event, filename) => {
91
96
  if (filename && CONFIG_FILENAMES.includes(filename)) schedule(true);
92
97
  })
93
98
  ];
99
+ rewatchContent = (dir) => {
100
+ contentWatcher.close();
101
+ contentWatcher = watch(dir, { recursive: true }, () => schedule());
102
+ console.log(`now watching ${relative(options.cwd, dir) || "."}`);
103
+ };
94
104
  const watchedContent = relative(options.cwd, config.contentDir) || ".";
95
105
  const branchLabel = branch === void 0 ? "static index" : branch.scope.kind === "physical" ? `${branch.name} (neon)` : options.branchId ?? "main";
96
106
  console.log(
@@ -99,6 +109,7 @@ async function devCommand(options) {
99
109
  await new Promise((resolveStopped) => {
100
110
  const stop = () => {
101
111
  clearTimeout(timer);
112
+ contentWatcher.close();
102
113
  for (const watcher of watchers) watcher.close();
103
114
  resolveStopped();
104
115
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  loadProjectEnv,
4
4
  requireDatabaseUrl
5
- } from "./chunk-BUPFXTUG.js";
5
+ } from "./chunk-2LG6TWYY.js";
6
6
 
7
7
  // src/commands/harden.ts
8
8
  async function hardenCommand(options) {
package/dist/index.js CHANGED
@@ -50,6 +50,11 @@ function printHelp() {
50
50
  " --port <n> Port for `graft serve` / `graft studio` (serve default 3903; studio 4983)",
51
51
  " --host <h> Host for `graft serve` / `graft studio` (default: 127.0.0.1, or HOST)",
52
52
  " --studio Mount opt-in Studio on `graft serve` at /studio (or GRAFT_STUDIO=1)",
53
+ " --elicit-approvals",
54
+ " `graft mcp` asks THIS terminal's operator to decide a destructive call,",
55
+ " instead of failing with an id for `graft approve`. Local only: the human",
56
+ " must be present and the client must support MCP elicitation. Decisions",
57
+ " are recorded as the OS user, exactly as `graft approve` records them.",
53
58
  " --from <name> Parent to fork from (branch create; default: main)",
54
59
  " --into <name> Merge target (merge; default: main)",
55
60
  " --backend <kind> Branch backend: overlay (default) or neon (branch create)",
@@ -80,6 +85,7 @@ function parseArgs(rest) {
80
85
  let overwrite = false;
81
86
  let pruneUnknown = false;
82
87
  let studio = false;
88
+ let elicitApprovals = false;
83
89
  let initDriver;
84
90
  const value = (flag, raw) => {
85
91
  if (!raw || raw.startsWith("-")) {
@@ -115,6 +121,8 @@ function parseArgs(rest) {
115
121
  pruneUnknown = true;
116
122
  } else if (arg === "--studio") {
117
123
  studio = true;
124
+ } else if (arg === "--elicit-approvals") {
125
+ elicitApprovals = true;
118
126
  } else if (arg === "--postgres") {
119
127
  initDriver = "postgres";
120
128
  } else if (arg === "--static") {
@@ -138,6 +146,7 @@ function parseArgs(rest) {
138
146
  overwrite,
139
147
  pruneUnknown,
140
148
  studio,
149
+ elicitApprovals,
141
150
  initDriver
142
151
  };
143
152
  }
@@ -156,7 +165,7 @@ async function run(argv, options = {}) {
156
165
  const args = parseArgs(rest);
157
166
  switch (command) {
158
167
  case "init": {
159
- const { initCommand } = await import("./init-3H2IP5US.js");
168
+ const { initCommand } = await import("./init-FV7Q6VXM.js");
160
169
  const result = initCommand({
161
170
  targetDir: args.positionals[0] ?? cwd,
162
171
  driver: args.initDriver
@@ -187,12 +196,12 @@ async function run(argv, options = {}) {
187
196
  return 0;
188
197
  }
189
198
  case "compile": {
190
- const { compileCommand } = await import("./compile-AAUC4G7H.js");
199
+ const { compileCommand } = await import("./compile-AWPSEA7U.js");
191
200
  await compileCommand({ cwd, branchId: args.branchId, pruneUnknown: args.pruneUnknown });
192
201
  return 0;
193
202
  }
194
203
  case "dev": {
195
- const { devCommand } = await import("./dev-MX7TQOJ2.js");
204
+ const { devCommand } = await import("./dev-WHTIRWMR.js");
196
205
  await devCommand({ cwd, branchId: args.branchId });
197
206
  return 0;
198
207
  }
@@ -203,7 +212,7 @@ async function run(argv, options = {}) {
203
212
  subcommand ? `unknown db subcommand "${subcommand}" \u2014 the only one is \`graft db migrate\`` : "usage: graft db migrate [--dry-run]"
204
213
  );
205
214
  }
206
- const { dbMigrateCommand, formatMigrateResult } = await import("./db-VGDWG4QA.js");
215
+ const { dbMigrateCommand, formatMigrateResult } = await import("./db-5YMFKHGO.js");
207
216
  const result = await dbMigrateCommand({ cwd, dryRun: args.dryRun });
208
217
  console.log(formatMigrateResult(result));
209
218
  return 0;
@@ -213,7 +222,7 @@ async function run(argv, options = {}) {
213
222
  if (subcommand !== "put" || !file) {
214
223
  throw new UsageError("usage: graft asset put <file> [key]");
215
224
  }
216
- const { assetPutCommand } = await import("./asset-FPEL463J.js");
225
+ const { assetPutCommand } = await import("./asset-JWER6AOG.js");
217
226
  const result = await assetPutCommand({ cwd, file, key });
218
227
  console.log(`Uploaded ${result.key} (${result.contentType}, ${result.bytes} bytes)`);
219
228
  console.log(
@@ -228,7 +237,7 @@ async function run(argv, options = {}) {
228
237
  return 0;
229
238
  }
230
239
  case "approvals": {
231
- const { approvalsListCommand, formatApproval } = await import("./approvals-Z3EOK5AM.js");
240
+ const { approvalsListCommand, formatApproval } = await import("./approvals-4PAANLA4.js");
232
241
  const pending = await approvalsListCommand({ cwd });
233
242
  if (pending.length === 0) {
234
243
  console.log("No pending approvals.");
@@ -245,7 +254,7 @@ async function run(argv, options = {}) {
245
254
  case "deny": {
246
255
  const id = args.positionals[0];
247
256
  if (!id) throw new UsageError(`usage: graft ${command} <approval-id>`);
248
- const { decideCommand } = await import("./approvals-Z3EOK5AM.js");
257
+ const { decideCommand } = await import("./approvals-4PAANLA4.js");
249
258
  const decision = command === "approve" ? "approved" : "denied";
250
259
  const row = await decideCommand({ cwd, id, decision });
251
260
  console.log(
@@ -259,7 +268,7 @@ async function run(argv, options = {}) {
259
268
  return 0;
260
269
  }
261
270
  case "migrate": {
262
- const { migrateCommand } = await import("./migrate-RRW4Z6RB.js");
271
+ const { migrateCommand } = await import("./migrate-6ZYG2H5C.js");
263
272
  await migrateCommand({ cwd, branchId: args.branchId, apply: args.apply });
264
273
  return 0;
265
274
  }
@@ -273,7 +282,7 @@ async function run(argv, options = {}) {
273
282
  if (subcommand && !name) {
274
283
  throw new UsageError(`usage: graft branch ${subcommand} <name>`);
275
284
  }
276
- const mod = await import("./branch-HRG46YIL.js");
285
+ const mod = await import("./branch-U7FFDTFV.js");
277
286
  if (!subcommand) {
278
287
  const rows = await mod.branchListCommand({ cwd });
279
288
  if (rows.length === 0) {
@@ -316,12 +325,12 @@ async function run(argv, options = {}) {
316
325
  const branch = args.positionals[0];
317
326
  if (!branch)
318
327
  throw new UsageError("usage: graft merge <branch> [--into <target>] [--apply]");
319
- const { mergeCommand } = await import("./merge-CZUNMMB5.js");
328
+ const { mergeCommand } = await import("./merge-ZWYZJVFE.js");
320
329
  await mergeCommand({ cwd, branch, into: args.into, apply: args.apply });
321
330
  return 0;
322
331
  }
323
332
  case "add": {
324
- const { addCommand } = await import("./add-UBAZMSP7.js");
333
+ const { addCommand } = await import("./add-TCQY3YAN.js");
325
334
  await addCommand({
326
335
  cwd,
327
336
  names: args.positionals,
@@ -331,12 +340,12 @@ async function run(argv, options = {}) {
331
340
  return 0;
332
341
  }
333
342
  case "mcp": {
334
- const { mcpCommand } = await import("./mcp-US3QFLOR.js");
335
- await mcpCommand({ cwd, branchId: args.branchId });
343
+ const { mcpCommand } = await import("./mcp-77QFFJXK.js");
344
+ await mcpCommand({ cwd, branchId: args.branchId, elicitApprovals: args.elicitApprovals });
336
345
  return 0;
337
346
  }
338
347
  case "serve": {
339
- const { serveCommand } = await import("./serve-Q63UTTER.js");
348
+ const { serveCommand } = await import("./serve-5ADUQG66.js");
340
349
  await serveCommand({
341
350
  cwd,
342
351
  branchId: args.branchId,
@@ -347,7 +356,7 @@ async function run(argv, options = {}) {
347
356
  return 0;
348
357
  }
349
358
  case "studio": {
350
- const { studioCommand } = await import("./studio-TNPOB665.js");
359
+ const { studioCommand } = await import("./studio-7VAYFEWS.js");
351
360
  await studioCommand({
352
361
  cwd,
353
362
  branchId: args.branchId,
@@ -357,7 +366,7 @@ async function run(argv, options = {}) {
357
366
  return 0;
358
367
  }
359
368
  case "content": {
360
- const { contentListCommand, formatContentLine } = await import("./content-DY2YI3LT.js");
369
+ const { contentListCommand, formatContentLine } = await import("./content-ZULMTJMD.js");
361
370
  const { branch, lines } = await contentListCommand({ cwd, branchId: args.branchId });
362
371
  if (lines.length === 0) {
363
372
  console.log(`No content on branch "${branch}" (run graft compile).`);
@@ -369,7 +378,7 @@ async function run(argv, options = {}) {
369
378
  return 0;
370
379
  }
371
380
  case "compilations": {
372
- const { compilationsListCommand, formatCompilation } = await import("./compilations-6P45MPAE.js");
381
+ const { compilationsListCommand, formatCompilation } = await import("./compilations-CH6AIOFG.js");
373
382
  const rows = await compilationsListCommand({ cwd, branchId: args.branchId });
374
383
  if (rows.length === 0) {
375
384
  console.log("No compilations recorded yet (run graft compile).");
@@ -381,7 +390,7 @@ async function run(argv, options = {}) {
381
390
  case "harden": {
382
391
  const role = args.positionals[0];
383
392
  if (!role) throw new UsageError("usage: graft harden <role>");
384
- const { hardenCommand } = await import("./harden-HQHONZPH.js");
393
+ const { hardenCommand } = await import("./harden-QONL2MAO.js");
385
394
  const result = await hardenCommand({ cwd, role });
386
395
  console.log(`Hardened runtime role "${result.role}" (${result.statements.length} grants):`);
387
396
  for (const statement of result.statements) console.log(` ${statement}`);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_FILENAMES
4
- } from "./chunk-BUPFXTUG.js";
4
+ } from "./chunk-2LG6TWYY.js";
5
5
 
6
6
  // src/commands/init.ts
7
7
  import { existsSync, mkdirSync, writeFileSync } from "fs";
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  assertNoStaticBranch
4
- } from "./chunk-KZ3FDWUK.js";
4
+ } from "./chunk-2N3HXYNN.js";
5
5
  import "./chunk-GPA66AP5.js";
6
+ import {
7
+ operatorName
8
+ } from "./chunk-NBJ4N4O6.js";
6
9
  import {
7
10
  findConfig,
8
11
  loadConfig,
9
12
  loadProjectEnv,
10
13
  requireDatabaseUrl
11
- } from "./chunk-BUPFXTUG.js";
14
+ } from "./chunk-2LG6TWYY.js";
12
15
 
13
16
  // src/commands/mcp.ts
14
17
  import { createActorResolver } from "@usegraft/auth";
@@ -25,21 +28,26 @@ async function mcpCommand(options) {
25
28
  try {
26
29
  const devToken = process.env.GRAFT_DEV_TOKEN;
27
30
  const scopes = (process.env.GRAFT_DEV_SCOPES ?? "").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
31
+ const cliActor = {
32
+ kind: "agent",
33
+ id: "graft-cli",
34
+ scopes: [.../* @__PURE__ */ new Set(["content:write", ...scopes])]
35
+ };
28
36
  const resolveActor = createActorResolver({
29
37
  issuers: [],
30
- devTokens: devToken ? {
31
- [devToken]: {
32
- kind: "agent",
33
- id: "graft-cli",
34
- scopes
35
- }
36
- } : void 0
38
+ devTokens: devToken ? { [devToken]: cliActor } : void 0
37
39
  });
40
+ const resolveStdioActor = async (request) => {
41
+ const resolved = await resolveActor(request);
42
+ return resolved.kind === "anonymous" ? cliActor : resolved;
43
+ };
44
+ const approvalElicitation = options.elicitApprovals ? { decider: { kind: "human", id: operatorName() } } : void 0;
38
45
  const server = createGraftMcp({
39
46
  name: "graft",
40
47
  contentDir: config.contentDir,
41
48
  collections: config.collections,
42
49
  functions: config.functions,
50
+ mdxTrust: config.mdxTrust,
43
51
  ...branch !== void 0 ? {
44
52
  db: branch.db,
45
53
  branchId: scopeWriteBranch(branch.scope),
@@ -47,8 +55,13 @@ async function mcpCommand(options) {
47
55
  // overlay branch searches its full parent chain, not just its own rows.
48
56
  scope: branch.scope
49
57
  } : { staticIndexPath: config.index.path },
50
- actor: resolveActor,
51
- defaultAuthorization: devToken
58
+ actor: resolveStdioActor,
59
+ connectionActor: cliActor,
60
+ // Local agents may upload a file from the project; remote mounts get no
61
+ // `path` argument at all.
62
+ localUploadRoot: config.projectDir,
63
+ defaultAuthorization: devToken,
64
+ approvalElicitation
52
65
  });
53
66
  await serveStdio(server);
54
67
  } finally {
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  discoverMigrations
4
- } from "./chunk-LTFTDG6Z.js";
4
+ } from "./chunk-DW6WTDRX.js";
5
5
  import {
6
6
  findConfig,
7
7
  loadConfig,
8
8
  loadProjectEnv,
9
9
  requireDatabaseUrl
10
- } from "./chunk-BUPFXTUG.js";
10
+ } from "./chunk-2LG6TWYY.js";
11
11
 
12
12
  // src/commands/merge.ts
13
13
  import { basename } from "path";
@@ -144,6 +144,7 @@ async function mergeCommand(options) {
144
144
  db: target.db,
145
145
  contentDir: config.contentDir,
146
146
  collections: config.collections,
147
+ mdxTrust: config.mdxTrust,
147
148
  branchId: targetWrite,
148
149
  gitSha
149
150
  });
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  discoverMigrations,
4
4
  migrateCommand
5
- } from "./chunk-LTFTDG6Z.js";
6
- import "./chunk-BUPFXTUG.js";
5
+ } from "./chunk-DW6WTDRX.js";
6
+ import "./chunk-2LG6TWYY.js";
7
7
  export {
8
8
  discoverMigrations,
9
9
  migrateCommand
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ allowedHostsFor,
3
4
  createNodeListener,
4
5
  createServeRouter,
5
6
  serveCommand,
6
7
  startServe
7
- } from "./chunk-TUWL7WFD.js";
8
- import "./chunk-BUPFXTUG.js";
8
+ } from "./chunk-CONTLKSZ.js";
9
+ import "./chunk-2LG6TWYY.js";
9
10
  export {
11
+ allowedHostsFor,
10
12
  createNodeListener,
11
13
  createServeRouter,
12
14
  serveCommand,
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ allowedHostsFor,
3
4
  createNodeListener
4
- } from "./chunk-TUWL7WFD.js";
5
+ } from "./chunk-CONTLKSZ.js";
5
6
  import {
6
7
  findConfig,
7
8
  loadConfig,
8
9
  loadProjectEnv,
9
10
  requireDatabaseUrl
10
- } from "./chunk-BUPFXTUG.js";
11
+ } from "./chunk-2LG6TWYY.js";
11
12
 
12
13
  // src/commands/studio.ts
13
14
  import { execFile } from "child_process";
@@ -17,11 +18,11 @@ import { GraftError } from "@usegraft/contracts";
17
18
  function isLoopback(host) {
18
19
  return host === "127.0.0.1" || host === "localhost" || host === "::1";
19
20
  }
20
- function operatorName() {
21
+ function operatorIdentity() {
21
22
  try {
22
- return userInfo().username;
23
+ return { kind: "human", id: userInfo().username };
23
24
  } catch {
24
- return process.env.USERNAME ?? process.env.USER ?? "operator";
25
+ return { kind: "human", id: process.env.USERNAME ?? process.env.USER ?? "operator" };
25
26
  }
26
27
  }
27
28
  function openBrowser(url) {
@@ -58,19 +59,24 @@ async function studioCommand(options) {
58
59
  "[graft studio] WARNING: binding beyond loopback with no GRAFT_DEV_TOKEN \u2014 set a bearer token before exposing Studio to a network."
59
60
  );
60
61
  }
61
- const authorize = !loopback && devToken ? (request) => {
62
+ const authenticate = !loopback && devToken ? (request) => {
62
63
  const header = request.headers.get("authorization") ?? "";
63
- return header === `Bearer ${devToken}`;
64
- } : !loopback ? () => false : void 0;
64
+ if (header !== `Bearer ${devToken}`) return null;
65
+ return {
66
+ ...operatorIdentity(),
67
+ scopes: ["studio:read", "studio:write", "approvals:decide"]
68
+ };
69
+ } : !loopback ? () => null : void 0;
65
70
  const handler = createStudioHandler({
66
71
  db: branch.db,
67
72
  collections: config.collections,
68
73
  contentDir: config.contentDir,
74
+ mdxTrust: config.mdxTrust,
69
75
  defaultBranch: writeBranch,
70
- decidedBy: operatorName,
71
- authorize
76
+ decider: operatorIdentity,
77
+ authenticate
72
78
  });
73
- const server = createServer(createNodeListener(handler));
79
+ const server = createServer(createNodeListener(handler, { allowedHosts: allowedHostsFor(host) }));
74
80
  await new Promise((resolve, reject) => {
75
81
  server.once("error", reject);
76
82
  server.listen(requestedPort, host, () => resolve());
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "name": "@usegraft/cli",
3
- "version": "0.1.1",
3
+ "version": "1.0.0-beta.0",
4
+ "description": "The graft CLI: scaffold, compile authored content, serve the runtime, and split your database credentials.",
5
+ "keywords": [
6
+ "agent",
7
+ "ai",
8
+ "cli",
9
+ "cms",
10
+ "content",
11
+ "graft",
12
+ "headless-cms",
13
+ "mcp",
14
+ "postgres",
15
+ "scaffolding",
16
+ "static-site",
17
+ "typescript"
18
+ ],
19
+ "homepage": "https://github.com/AndersonDesign1/graft#readme",
4
20
  "license": "MIT",
5
21
  "repository": {
6
22
  "type": "git",
@@ -18,20 +34,22 @@
18
34
  "access": "public"
19
35
  },
20
36
  "dependencies": {
21
- "@usegraft/assets": "0.1.1",
22
- "@usegraft/auth": "0.1.1",
23
- "@usegraft/compiler": "0.1.1",
24
- "@usegraft/content-migrations": "0.1.1",
25
- "@usegraft/contracts": "0.1.1",
26
- "@usegraft/core": "0.1.1",
27
- "@usegraft/db": "0.1.1",
28
- "@usegraft/mcp": "0.1.1",
29
- "@usegraft/registry": "0.1.1",
30
- "@usegraft/studio": "0.1.1",
37
+ "@usegraft/assets": "1.0.0-beta.0",
38
+ "@usegraft/auth": "1.0.0-beta.0",
39
+ "@usegraft/compiler": "1.0.0-beta.0",
40
+ "@usegraft/content-api": "1.0.0-beta.0",
41
+ "@usegraft/content-migrations": "1.0.0-beta.0",
42
+ "@usegraft/contracts": "1.0.0-beta.0",
43
+ "@usegraft/core": "1.0.0-beta.0",
44
+ "@usegraft/db": "1.0.0-beta.0",
45
+ "@usegraft/mcp": "1.0.0-beta.0",
46
+ "@usegraft/mdx-safety": "1.0.0-beta.0",
47
+ "@usegraft/registry": "1.0.0-beta.0",
48
+ "@usegraft/studio": "1.0.0-beta.0",
31
49
  "jiti": "^2.4.0"
32
50
  },
33
51
  "devDependencies": {
34
- "@usegraft/sdk-core": "0.1.1"
52
+ "@usegraft/sdk-core": "1.0.0-beta.0"
35
53
  },
36
54
  "engines": {
37
55
  "node": ">=22.16"
@@ -40,7 +58,6 @@
40
58
  "build": "tsup",
41
59
  "dev": "tsup --watch",
42
60
  "typecheck": "tsc --noEmit",
43
- "test": "vitest run --passWithNoTests",
44
- "lint": "oxlint ."
61
+ "test": "vitest run --passWithNoTests"
45
62
  }
46
63
  }