@usegraft/cli 1.0.0-beta.0 → 1.0.0-beta.2

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 CHANGED
@@ -9,7 +9,7 @@ Part of [Graft](https://github.com/AndersonDesign1/graft), a CMS built so an AI
9
9
  ```bash
10
10
  npm i -D @usegraft/cli
11
11
  # or run it without installing
12
- npx @usegraft/cli init
12
+ npx @usegraft/cli@latest init
13
13
  ```
14
14
 
15
15
  Once installed, the command is `graft`. Run it without installing under the
@@ -21,8 +21,8 @@ package, so `npx graft` fetches the wrong thing.
21
21
  A content project needs no services at all. `graft compile` writes the index to a SQLite artifact your app reads embedded.
22
22
 
23
23
  ```bash
24
- npx @usegraft/cli init # scaffolds graft.config.ts, content/, llms.txt
25
- npx @usegraft/cli compile # → .graft/index.db
24
+ npx @usegraft/cli@latest init # scaffolds graft.config.ts, content/, llms.txt
25
+ npx @usegraft/cli@latest compile # → .graft/index.db
26
26
  ```
27
27
 
28
28
  Move to Postgres by setting `DATABASE_URL` and changing one line in `graft.config.ts`:
@@ -102,7 +102,7 @@ function createNodeListener(handler, options = {}) {
102
102
  res.end(
103
103
  JSON.stringify({
104
104
  error: "FUNCTION_EXECUTION_FAILED",
105
- message: `graft serve failed to relay the request: ${error instanceof Error ? error.message : String(error)}`,
105
+ message: "graft serve failed to relay the request.",
106
106
  fix: "Check the server logs; if this reproduces, file it \u2014 the adapter should never throw."
107
107
  })
108
108
  );
@@ -208,14 +208,11 @@ async function startServe(options) {
208
208
  const health = async () => {
209
209
  try {
210
210
  await branch.db.execute(sql`select 1`);
211
- } catch (error) {
212
- return new Response(
213
- JSON.stringify({
214
- ok: false,
215
- error: error instanceof Error ? error.message : String(error)
216
- }),
217
- { status: 503, headers: { "content-type": "application/json" } }
218
- );
211
+ } catch {
212
+ return new Response(JSON.stringify({ ok: false, error: "database_unavailable" }), {
213
+ status: 503,
214
+ headers: { "content-type": "application/json" }
215
+ });
219
216
  }
220
217
  return new Response(
221
218
  JSON.stringify({
package/dist/index.js CHANGED
@@ -345,7 +345,7 @@ async function run(argv, options = {}) {
345
345
  return 0;
346
346
  }
347
347
  case "serve": {
348
- const { serveCommand } = await import("./serve-5ADUQG66.js");
348
+ const { serveCommand } = await import("./serve-OP4OPK37.js");
349
349
  await serveCommand({
350
350
  cwd,
351
351
  branchId: args.branchId,
@@ -356,7 +356,7 @@ async function run(argv, options = {}) {
356
356
  return 0;
357
357
  }
358
358
  case "studio": {
359
- const { studioCommand } = await import("./studio-7VAYFEWS.js");
359
+ const { studioCommand } = await import("./studio-HUGOTZOY.js");
360
360
  await studioCommand({
361
361
  cwd,
362
362
  branchId: args.branchId,
@@ -5,7 +5,7 @@ import {
5
5
  createServeRouter,
6
6
  serveCommand,
7
7
  startServe
8
- } from "./chunk-CONTLKSZ.js";
8
+ } from "./chunk-PSJ2OBWG.js";
9
9
  import "./chunk-2LG6TWYY.js";
10
10
  export {
11
11
  allowedHostsFor,
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ allowedHostsFor,
4
+ createNodeListener
5
+ } from "./chunk-PSJ2OBWG.js";
6
+ import {
7
+ findConfig,
8
+ loadConfig,
9
+ loadProjectEnv,
10
+ requireDatabaseUrl
11
+ } from "./chunk-2LG6TWYY.js";
12
+
13
+ // src/commands/studio.ts
14
+ import { execFile } from "child_process";
15
+ import { createServer } from "http";
16
+ import { userInfo } from "os";
17
+ import { GraftError } from "@usegraft/contracts";
18
+ function isLoopback(host) {
19
+ return host === "127.0.0.1" || host === "localhost" || host === "::1";
20
+ }
21
+ function operatorIdentity() {
22
+ try {
23
+ return { kind: "human", id: userInfo().username };
24
+ } catch {
25
+ return { kind: "human", id: process.env.USERNAME ?? process.env.USER ?? "operator" };
26
+ }
27
+ }
28
+ function isGitPreviewBranch(branch) {
29
+ if (branch === "" || branch === "@") return false;
30
+ if (branch.includes("..") || branch.includes("@{") || branch.includes("//") || branch.includes("\\")) {
31
+ return false;
32
+ }
33
+ if (branch.startsWith("/") || branch.endsWith("/") || branch.endsWith(".")) return false;
34
+ if (!/^[A-Za-z0-9._/@+-]+$/.test(branch)) return false;
35
+ return branch.split("/").every((part) => part.length > 0 && !part.startsWith(".") && !part.endsWith(".lock"));
36
+ }
37
+ function assertGitPreviewBranch(branch) {
38
+ if (isGitPreviewBranch(branch)) return;
39
+ throw new GraftError({
40
+ code: "INPUT_VALIDATION_FAILED",
41
+ message: `Branch "${branch}" is not a safe preview query value.`,
42
+ fix: "Use a git-ref-shaped branch name (letters, digits, . _ / @ + -)."
43
+ });
44
+ }
45
+ function studioPreviewUrl(port, branch) {
46
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
47
+ throw new GraftError({
48
+ code: "INPUT_VALIDATION_FAILED",
49
+ message: `"${port}" is not a valid bound port.`,
50
+ fix: "Pass --port <1-65535> (0 picks a free port, then we read the one that bound)."
51
+ });
52
+ }
53
+ assertGitPreviewBranch(branch);
54
+ const preview = new URL(`http://127.0.0.1:${port}/`);
55
+ preview.searchParams.set("branch", branch);
56
+ if (preview.protocol !== "http:" || preview.hostname !== "127.0.0.1") {
57
+ throw new GraftError({
58
+ code: "INPUT_VALIDATION_FAILED",
59
+ message: `Preview URL is not loopback http.`,
60
+ fix: "graft studio only opens http://127.0.0.1."
61
+ });
62
+ }
63
+ return preview.href;
64
+ }
65
+ function openBrowser(url) {
66
+ let parsed;
67
+ try {
68
+ parsed = new URL(url);
69
+ } catch {
70
+ return;
71
+ }
72
+ if (parsed.protocol !== "http:" || parsed.hostname !== "127.0.0.1") return;
73
+ const platform = process.platform;
74
+ const cmd = platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
75
+ const args = platform === "win32" ? ["/c", "start", "", parsed.href] : [parsed.href];
76
+ execFile(cmd, args, () => {
77
+ });
78
+ }
79
+ async function studioCommand(options) {
80
+ loadProjectEnv(options.cwd);
81
+ const config = await loadConfig(findConfig(options.cwd));
82
+ const url = requireDatabaseUrl();
83
+ const [{ createStudioHandler }, { createDb, resolveBranchHandle, scopeWriteBranch }] = await Promise.all([import("@usegraft/studio"), import("@usegraft/db")]);
84
+ const branchName = options.branchId ?? "main";
85
+ assertGitPreviewBranch(branchName);
86
+ const control = createDb(url);
87
+ try {
88
+ const branch = await resolveBranchHandle(control.db, branchName, { databaseUrl: url });
89
+ const writeBranch = scopeWriteBranch(branch.scope);
90
+ let server;
91
+ try {
92
+ const host = options.host ?? process.env.HOST ?? "127.0.0.1";
93
+ const requestedPort = options.port ?? Number(process.env.GRAFT_STUDIO_PORT ?? 4983);
94
+ if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65535) {
95
+ throw new GraftError({
96
+ code: "INPUT_VALIDATION_FAILED",
97
+ message: `"${options.port ?? process.env.GRAFT_STUDIO_PORT}" is not a valid port.`,
98
+ fix: "Pass --port <0-65535> (0 picks a free port)."
99
+ });
100
+ }
101
+ const loopback = isLoopback(host);
102
+ const devToken = process.env.GRAFT_DEV_TOKEN;
103
+ if (!loopback && !devToken) {
104
+ console.warn(
105
+ "[graft studio] WARNING: binding beyond loopback with no GRAFT_DEV_TOKEN \u2014 set a bearer token before exposing Studio to a network."
106
+ );
107
+ }
108
+ const authenticate = !loopback && devToken ? (request) => {
109
+ const header = request.headers.get("authorization") ?? "";
110
+ if (header !== `Bearer ${devToken}`) return null;
111
+ return {
112
+ ...operatorIdentity(),
113
+ scopes: ["studio:read", "studio:write", "approvals:decide"]
114
+ };
115
+ } : !loopback ? () => null : void 0;
116
+ const handler = createStudioHandler({
117
+ db: branch.db,
118
+ collections: config.collections,
119
+ contentDir: config.contentDir,
120
+ mdxTrust: config.mdxTrust,
121
+ defaultBranch: writeBranch,
122
+ decider: operatorIdentity,
123
+ authenticate
124
+ });
125
+ const listening = createServer(
126
+ createNodeListener(handler, { allowedHosts: allowedHostsFor(host) })
127
+ );
128
+ server = listening;
129
+ await new Promise((resolve, reject) => {
130
+ listening.once("error", reject);
131
+ listening.listen(requestedPort, host, () => resolve());
132
+ });
133
+ const address = listening.address();
134
+ const port = typeof address === "object" && address ? address.port : requestedPort;
135
+ const origin = loopback ? `http://127.0.0.1:${port}` : `http://${host}:${port}`;
136
+ console.log(
137
+ [
138
+ `graft studio \u2014 branch "${branchName}" (opt-in)`,
139
+ ` ui GET ${origin}/`,
140
+ ` openapi GET ${origin}/api/studio/v1/openapi.json`,
141
+ ` Edit content, approve/deny \u2014 same ops as MCP/CLI.`
142
+ ].join("\n")
143
+ );
144
+ if (loopback) openBrowser(studioPreviewUrl(port, branchName));
145
+ await new Promise((resolve) => {
146
+ const stop = () => resolve();
147
+ process.once("SIGINT", stop);
148
+ process.once("SIGTERM", stop);
149
+ });
150
+ } finally {
151
+ const bound = server;
152
+ if (bound?.listening) {
153
+ await new Promise((resolve, reject) => {
154
+ bound.close((err) => err ? reject(err) : resolve());
155
+ });
156
+ }
157
+ await branch.close();
158
+ }
159
+ } finally {
160
+ await control.close();
161
+ }
162
+ }
163
+ export {
164
+ studioCommand,
165
+ studioPreviewUrl
166
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usegraft/cli",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "The graft CLI: scaffold, compile authored content, serve the runtime, and split your database credentials.",
5
5
  "keywords": [
6
6
  "agent",
@@ -34,22 +34,22 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
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",
49
- "jiti": "^2.4.0"
37
+ "@usegraft/assets": "1.0.0-beta.2",
38
+ "@usegraft/auth": "1.0.0-beta.2",
39
+ "@usegraft/compiler": "1.0.0-beta.2",
40
+ "@usegraft/content-api": "1.0.0-beta.2",
41
+ "@usegraft/content-migrations": "1.0.0-beta.2",
42
+ "@usegraft/contracts": "1.0.0-beta.2",
43
+ "@usegraft/core": "1.0.0-beta.2",
44
+ "@usegraft/db": "1.0.0-beta.2",
45
+ "@usegraft/mcp": "1.0.0-beta.2",
46
+ "@usegraft/mdx-safety": "1.0.0-beta.2",
47
+ "@usegraft/registry": "1.0.0-beta.2",
48
+ "@usegraft/studio": "1.0.0-beta.2",
49
+ "jiti": "^2.7.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@usegraft/sdk-core": "1.0.0-beta.0"
52
+ "@usegraft/sdk-core": "1.0.0-beta.2"
53
53
  },
54
54
  "engines": {
55
55
  "node": ">=22.16"
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- allowedHostsFor,
4
- createNodeListener
5
- } from "./chunk-CONTLKSZ.js";
6
- import {
7
- findConfig,
8
- loadConfig,
9
- loadProjectEnv,
10
- requireDatabaseUrl
11
- } from "./chunk-2LG6TWYY.js";
12
-
13
- // src/commands/studio.ts
14
- import { execFile } from "child_process";
15
- import { createServer } from "http";
16
- import { userInfo } from "os";
17
- import { GraftError } from "@usegraft/contracts";
18
- function isLoopback(host) {
19
- return host === "127.0.0.1" || host === "localhost" || host === "::1";
20
- }
21
- function operatorIdentity() {
22
- try {
23
- return { kind: "human", id: userInfo().username };
24
- } catch {
25
- return { kind: "human", id: process.env.USERNAME ?? process.env.USER ?? "operator" };
26
- }
27
- }
28
- function openBrowser(url) {
29
- const platform = process.platform;
30
- const cmd = platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
31
- const args = platform === "win32" ? ["/c", "start", "", url] : [url];
32
- execFile(cmd, args, () => {
33
- });
34
- }
35
- async function studioCommand(options) {
36
- loadProjectEnv(options.cwd);
37
- const config = await loadConfig(findConfig(options.cwd));
38
- const url = requireDatabaseUrl();
39
- const [{ createStudioHandler }, { createDb, resolveBranchHandle, scopeWriteBranch }] = await Promise.all([import("@usegraft/studio"), import("@usegraft/db")]);
40
- const control = createDb(url);
41
- const branchName = options.branchId ?? "main";
42
- const branch = await resolveBranchHandle(control.db, branchName, { databaseUrl: url });
43
- const writeBranch = scopeWriteBranch(branch.scope);
44
- const host = options.host ?? process.env.HOST ?? "127.0.0.1";
45
- const requestedPort = options.port ?? Number(process.env.GRAFT_STUDIO_PORT ?? 4983);
46
- if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65535) {
47
- await branch.close();
48
- await control.close();
49
- throw new GraftError({
50
- code: "INPUT_VALIDATION_FAILED",
51
- message: `"${options.port ?? process.env.GRAFT_STUDIO_PORT}" is not a valid port.`,
52
- fix: "Pass --port <0-65535> (0 picks a free port)."
53
- });
54
- }
55
- const loopback = isLoopback(host);
56
- const devToken = process.env.GRAFT_DEV_TOKEN;
57
- if (!loopback && !devToken) {
58
- console.warn(
59
- "[graft studio] WARNING: binding beyond loopback with no GRAFT_DEV_TOKEN \u2014 set a bearer token before exposing Studio to a network."
60
- );
61
- }
62
- const authenticate = !loopback && devToken ? (request) => {
63
- const header = request.headers.get("authorization") ?? "";
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;
70
- const handler = createStudioHandler({
71
- db: branch.db,
72
- collections: config.collections,
73
- contentDir: config.contentDir,
74
- mdxTrust: config.mdxTrust,
75
- defaultBranch: writeBranch,
76
- decider: operatorIdentity,
77
- authenticate
78
- });
79
- const server = createServer(createNodeListener(handler, { allowedHosts: allowedHostsFor(host) }));
80
- await new Promise((resolve, reject) => {
81
- server.once("error", reject);
82
- server.listen(requestedPort, host, () => resolve());
83
- });
84
- const address = server.address();
85
- const port = typeof address === "object" && address ? address.port : requestedPort;
86
- const base = `http://${host}:${port}`;
87
- console.log(
88
- [
89
- `graft studio \u2014 branch "${branchName}" (opt-in)`,
90
- ` ui GET ${base}/`,
91
- ` openapi GET ${base}/api/studio/v1/openapi.json`,
92
- ` Edit content, approve/deny \u2014 same ops as MCP/CLI.`
93
- ].join("\n")
94
- );
95
- if (loopback) openBrowser(`${base}/?branch=${encodeURIComponent(branchName)}`);
96
- await new Promise((resolve) => {
97
- const stop = () => resolve();
98
- process.once("SIGINT", stop);
99
- process.once("SIGTERM", stop);
100
- });
101
- await new Promise((resolve, reject) => {
102
- server.close((err) => err ? reject(err) : resolve());
103
- });
104
- await branch.close();
105
- await control.close();
106
- }
107
- export {
108
- studioCommand
109
- };