erdraft 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +204 -2
- package/dist/chunk-SSLBX2OF.js +2162 -0
- package/dist/chunk-VSN3XJPO.js +99 -0
- package/dist/cli.js +507 -0
- package/dist/inspect/index.js +373 -0
- package/dist/inspect/logger.js +64 -0
- package/dist/read-schema-child.js +238 -0
- package/dist/studio-config-TIVT4MNY.js +32 -0
- package/dist/types/core/fields.d.ts +39 -0
- package/dist/types/inspect/field-of.d.ts +3 -0
- package/dist/types/inspect/holds-value.d.ts +2 -0
- package/dist/types/inspect/inspect-logger.d.ts +2 -0
- package/dist/types/inspect/inspector.d.ts +5 -0
- package/dist/types/inspect/listen-on-document.d.ts +5 -0
- package/dist/types/inspect/read-write.d.ts +5 -0
- package/dist/types/inspect/use-field-changes.d.ts +6 -0
- package/dist/types/inspect/watch-storage.d.ts +8 -0
- package/dist/window/apple-touch-icon.png +0 -0
- package/dist/window/assets/index-B5sqm8jk.css +2 -0
- package/dist/window/assets/index-Dk6ZQwtE.js +132 -0
- package/dist/window/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/window/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/window/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/window/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/window/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/window/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/window/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/window/favicon.ico +0 -0
- package/dist/window/favicon.png +0 -0
- package/dist/window/favicon.svg +1 -0
- package/dist/window/index.html +23 -0
- package/package.json +91 -3
- package/skill/SKILL.md +47 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/core/schema.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var REFERENTIAL_ACTIONS = [
|
|
4
|
+
"cascade",
|
|
5
|
+
"restrict",
|
|
6
|
+
"no action",
|
|
7
|
+
"set null",
|
|
8
|
+
"set default"
|
|
9
|
+
];
|
|
10
|
+
var columnSchema = z.object({
|
|
11
|
+
name: z.string().min(1),
|
|
12
|
+
type: z.string().min(1),
|
|
13
|
+
nullable: z.boolean().default(true),
|
|
14
|
+
primaryKey: z.boolean().default(false),
|
|
15
|
+
unique: z.boolean().default(false),
|
|
16
|
+
default: z.string().optional(),
|
|
17
|
+
note: z.string().optional(),
|
|
18
|
+
shape: z.string().min(1).optional()
|
|
19
|
+
});
|
|
20
|
+
var columnChangeSchema = z.object({
|
|
21
|
+
name: z.string().min(1),
|
|
22
|
+
type: z.string().min(1),
|
|
23
|
+
nullable: z.boolean(),
|
|
24
|
+
primaryKey: z.boolean(),
|
|
25
|
+
unique: z.boolean(),
|
|
26
|
+
default: z.string().nullable(),
|
|
27
|
+
note: z.string().nullable(),
|
|
28
|
+
shape: z.string().min(1).nullable()
|
|
29
|
+
}).partial();
|
|
30
|
+
var foreignKeySchema = z.object({
|
|
31
|
+
name: z.string().min(1).optional(),
|
|
32
|
+
columns: z.array(z.string().min(1)).min(1),
|
|
33
|
+
table: z.string().min(1),
|
|
34
|
+
foreignColumns: z.array(z.string().min(1)).min(1),
|
|
35
|
+
onDelete: z.enum(REFERENTIAL_ACTIONS).optional()
|
|
36
|
+
});
|
|
37
|
+
var indexSchema = z.object({
|
|
38
|
+
name: z.string().min(1).optional(),
|
|
39
|
+
columns: z.array(z.string().min(1)).min(1),
|
|
40
|
+
unique: z.boolean().default(false)
|
|
41
|
+
});
|
|
42
|
+
var checkSchema = z.object({
|
|
43
|
+
name: z.string().min(1),
|
|
44
|
+
expression: z.string().min(1)
|
|
45
|
+
});
|
|
46
|
+
var tableSchema = z.object({
|
|
47
|
+
name: z.string().min(1),
|
|
48
|
+
note: z.string().optional(),
|
|
49
|
+
columns: z.array(columnSchema).min(1),
|
|
50
|
+
foreignKeys: z.array(foreignKeySchema).default([]),
|
|
51
|
+
indexes: z.array(indexSchema).default([]),
|
|
52
|
+
checks: z.array(checkSchema).default([])
|
|
53
|
+
});
|
|
54
|
+
var enumSchema = z.object({
|
|
55
|
+
name: z.string().min(1),
|
|
56
|
+
values: z.array(z.string().min(1)).min(1)
|
|
57
|
+
});
|
|
58
|
+
var schemaSchema = z.object({
|
|
59
|
+
tables: z.array(tableSchema),
|
|
60
|
+
enums: z.array(enumSchema)
|
|
61
|
+
});
|
|
62
|
+
function foreignKeyName(table, foreignKey) {
|
|
63
|
+
return foreignKey.name ?? `${table}_${foreignKey.columns.join("_")}_${foreignKey.table}_${foreignKey.foreignColumns.join("_")}_fk`;
|
|
64
|
+
}
|
|
65
|
+
function indexName(table, index) {
|
|
66
|
+
return index.name ?? `${table}_${index.columns.join("_")}_${index.unique ? "unique" : "index"}`;
|
|
67
|
+
}
|
|
68
|
+
function namedTable(table) {
|
|
69
|
+
return {
|
|
70
|
+
...table,
|
|
71
|
+
foreignKeys: table.foreignKeys.map((foreignKey) => ({
|
|
72
|
+
...foreignKey,
|
|
73
|
+
name: foreignKeyName(table.name, foreignKey)
|
|
74
|
+
})),
|
|
75
|
+
indexes: table.indexes.map((index) => ({
|
|
76
|
+
...index,
|
|
77
|
+
name: indexName(table.name, index)
|
|
78
|
+
}))
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function parseSchema(value) {
|
|
82
|
+
const schema = schemaSchema.parse(value);
|
|
83
|
+
return { tables: schema.tables.map(namedTable), enums: schema.enums };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
columnSchema,
|
|
88
|
+
columnChangeSchema,
|
|
89
|
+
foreignKeySchema,
|
|
90
|
+
indexSchema,
|
|
91
|
+
checkSchema,
|
|
92
|
+
tableSchema,
|
|
93
|
+
enumSchema,
|
|
94
|
+
schemaSchema,
|
|
95
|
+
foreignKeyName,
|
|
96
|
+
indexName,
|
|
97
|
+
namedTable,
|
|
98
|
+
parseSchema
|
|
99
|
+
};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DECISION_SECONDS,
|
|
4
|
+
chooseDatabase,
|
|
5
|
+
createApi,
|
|
6
|
+
createServer,
|
|
7
|
+
currentDatabase,
|
|
8
|
+
databaseOf,
|
|
9
|
+
describeDecision,
|
|
10
|
+
describeDraft,
|
|
11
|
+
describeTable,
|
|
12
|
+
describeTables,
|
|
13
|
+
draftLine,
|
|
14
|
+
readDraft,
|
|
15
|
+
readSchema,
|
|
16
|
+
storeOf,
|
|
17
|
+
version_default,
|
|
18
|
+
writtenIn
|
|
19
|
+
} from "./chunk-SSLBX2OF.js";
|
|
20
|
+
import "./chunk-VSN3XJPO.js";
|
|
21
|
+
|
|
22
|
+
// bin/erdraft.ts
|
|
23
|
+
import { fileURLToPath } from "url";
|
|
24
|
+
import { existsSync } from "fs";
|
|
25
|
+
import { join as join4 } from "path";
|
|
26
|
+
import { text } from "stream/consumers";
|
|
27
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
28
|
+
|
|
29
|
+
// src/server/serve-window.ts
|
|
30
|
+
import { createServer as createServer2 } from "http";
|
|
31
|
+
import { readFile, stat } from "fs/promises";
|
|
32
|
+
import { extname, join } from "path";
|
|
33
|
+
var TYPES = {
|
|
34
|
+
".html": "text/html; charset=utf-8",
|
|
35
|
+
".js": "text/javascript; charset=utf-8",
|
|
36
|
+
".css": "text/css; charset=utf-8",
|
|
37
|
+
".svg": "image/svg+xml",
|
|
38
|
+
".png": "image/png",
|
|
39
|
+
".ico": "image/x-icon",
|
|
40
|
+
".woff2": "font/woff2"
|
|
41
|
+
};
|
|
42
|
+
async function fileOf(folder, url) {
|
|
43
|
+
const { pathname } = new URL(String(url), "http://localhost");
|
|
44
|
+
const path = join(folder, pathname);
|
|
45
|
+
const found = await stat(path).catch(() => void 0);
|
|
46
|
+
return found?.isFile() ? path : join(folder, "index.html");
|
|
47
|
+
}
|
|
48
|
+
function refuse(response, status, message) {
|
|
49
|
+
response.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
50
|
+
response.end(message);
|
|
51
|
+
}
|
|
52
|
+
async function serveWindow(config, folder) {
|
|
53
|
+
const api = createApi(config);
|
|
54
|
+
const server = createServer2((request, response) => {
|
|
55
|
+
api.middleware(request, response, (error) => {
|
|
56
|
+
if (error) {
|
|
57
|
+
refuse(response, 500, String(error));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
61
|
+
refuse(response, 404, "Not found");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
fileOf(folder, request.url).then(async (path) => {
|
|
65
|
+
const body = await readFile(path);
|
|
66
|
+
response.writeHead(200, {
|
|
67
|
+
"content-type": TYPES[extname(path)] ?? "application/octet-stream"
|
|
68
|
+
});
|
|
69
|
+
response.end(body);
|
|
70
|
+
}).catch((failure) => refuse(response, 500, String(failure)));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
server.on("close", api.close);
|
|
74
|
+
await new Promise((done, fail) => {
|
|
75
|
+
server.once("error", (error) => {
|
|
76
|
+
api.close();
|
|
77
|
+
fail(
|
|
78
|
+
error.code === "EADDRINUSE" ? new Error(
|
|
79
|
+
`Port ${config.port} is taken: stop what holds it, or give the window another "port" in .erdraft.json`
|
|
80
|
+
) : error
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
server.listen(config.port, "127.0.0.1", done);
|
|
84
|
+
});
|
|
85
|
+
return server;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/cli/run.ts
|
|
89
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
90
|
+
import { resolve as resolve2 } from "path";
|
|
91
|
+
import { parseArgs } from "util";
|
|
92
|
+
import { z as z2 } from "zod";
|
|
93
|
+
|
|
94
|
+
// src/core/load-config.ts
|
|
95
|
+
import { z } from "zod";
|
|
96
|
+
import { join as join2, resolve, dirname, basename } from "path";
|
|
97
|
+
import { homedir } from "os";
|
|
98
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
99
|
+
import { createHash } from "crypto";
|
|
100
|
+
var CONFIG_FILE = ".erdraft.json";
|
|
101
|
+
var databaseSchema = z.object({
|
|
102
|
+
// The name the window's menu shows it by
|
|
103
|
+
name: z.string().min(1),
|
|
104
|
+
// The module of Drizzle that exports every table and enum (drizzle-kit's `schema`)
|
|
105
|
+
schema: z.string().min(1)
|
|
106
|
+
});
|
|
107
|
+
var configSchema = z.object({
|
|
108
|
+
// The project's name; none given, its folder's
|
|
109
|
+
name: z.string().min(1).optional(),
|
|
110
|
+
// The project's one database, its module of Drizzle; or its databases, each named, the window switching between
|
|
111
|
+
schema: z.string().min(1).optional(),
|
|
112
|
+
databases: z.array(databaseSchema).min(1).optional(),
|
|
113
|
+
// The folder Erdraft keeps the drafts in, shared by the CLI, the MCP and the window, and by every workspace of
|
|
114
|
+
// the project on the machine: `~` is the home folder; none given, ~/.erdraft/<the project's name>
|
|
115
|
+
store: z.string().min(1).optional(),
|
|
116
|
+
// The window's port
|
|
117
|
+
port: z.number().int().min(1).max(65535).default(9369),
|
|
118
|
+
// The window's assistant: the Claude Code it runs headless, and its model (an alias or a model's full name)
|
|
119
|
+
claude: z.string().min(1).default("claude"),
|
|
120
|
+
model: z.string().min(1).default("sonnet")
|
|
121
|
+
});
|
|
122
|
+
function folderOf(name) {
|
|
123
|
+
const folder = name.normalize("NFKD").replace(new RegExp("\\p{Diacritic}", "gu"), "").toLocaleLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
124
|
+
return folder || createHash("sha256").update(name).digest("hex").slice(0, 12);
|
|
125
|
+
}
|
|
126
|
+
function storeOf2(folder, name, store) {
|
|
127
|
+
if (store === void 0) {
|
|
128
|
+
return join2(homedir(), ".erdraft", folderOf(name));
|
|
129
|
+
}
|
|
130
|
+
if (store === "~" || store.startsWith("~/")) {
|
|
131
|
+
return join2(homedir(), store.slice(1));
|
|
132
|
+
}
|
|
133
|
+
return resolve(folder, store);
|
|
134
|
+
}
|
|
135
|
+
function parsed(path, text2) {
|
|
136
|
+
try {
|
|
137
|
+
return configSchema.parse(JSON.parse(text2));
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`${path} is not a config Erdraft reads: ${error instanceof z.ZodError ? z.prettifyError(error) : String(error)}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function readIfThere(path) {
|
|
145
|
+
try {
|
|
146
|
+
return await readFile2(path, "utf8");
|
|
147
|
+
} catch {
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function loadConfig(from) {
|
|
152
|
+
let folder = resolve(from);
|
|
153
|
+
for (; ; ) {
|
|
154
|
+
const text2 = await readIfThere(join2(folder, CONFIG_FILE));
|
|
155
|
+
if (text2 !== void 0) {
|
|
156
|
+
const {
|
|
157
|
+
schema,
|
|
158
|
+
databases,
|
|
159
|
+
name = basename(folder),
|
|
160
|
+
...config
|
|
161
|
+
} = parsed(join2(folder, CONFIG_FILE), text2);
|
|
162
|
+
const [first, ...rest] = (databases ?? (schema ? [{ name, schema }] : [])).map((database) => ({
|
|
163
|
+
id: folderOf(database.name),
|
|
164
|
+
name: database.name,
|
|
165
|
+
schema: resolve(folder, database.schema)
|
|
166
|
+
}));
|
|
167
|
+
if (!first) {
|
|
168
|
+
throw new Error(`${CONFIG_FILE} names no schema nor databases`);
|
|
169
|
+
}
|
|
170
|
+
const ids = new Set([first, ...rest].map((database) => database.id));
|
|
171
|
+
if (ids.size !== rest.length + 1) {
|
|
172
|
+
throw new Error(`${CONFIG_FILE} names two databases alike`);
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
...config,
|
|
176
|
+
name,
|
|
177
|
+
databases: [first, ...rest],
|
|
178
|
+
store: storeOf2(folder, name, config.store),
|
|
179
|
+
root: folder,
|
|
180
|
+
workspace: createHash("sha256").update(folder).digest("hex").slice(0, 12)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const parent = dirname(folder);
|
|
184
|
+
if (parent === folder) {
|
|
185
|
+
throw new Error(`No ${CONFIG_FILE} in ${resolve(from)} or above it`);
|
|
186
|
+
}
|
|
187
|
+
folder = parent;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/core/install-skill.ts
|
|
192
|
+
import { mkdir, readFile as readFile3, writeFile } from "fs/promises";
|
|
193
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
194
|
+
var SKILL_PATH = join3(".claude", "skills", "erdraft", "SKILL.md");
|
|
195
|
+
async function readIfThere2(path) {
|
|
196
|
+
try {
|
|
197
|
+
return await readFile3(path, "utf8");
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
200
|
+
return void 0;
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function installSkill(root, source, force = false) {
|
|
206
|
+
const path = join3(root, SKILL_PATH);
|
|
207
|
+
const skill = await readFile3(source, "utf8");
|
|
208
|
+
const there = await readIfThere2(path);
|
|
209
|
+
if (there === skill) {
|
|
210
|
+
return { path, written: false };
|
|
211
|
+
}
|
|
212
|
+
if (there !== void 0 && !force) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`${SKILL_PATH} differs from this Erdraft's skill (changed, or of another version): --force writes it over`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
218
|
+
await writeFile(path, skill);
|
|
219
|
+
return { path, written: true };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/cli/run.ts
|
|
223
|
+
var HELP = `erdraft: a project's data model, drafted with an agent while a person watches
|
|
224
|
+
|
|
225
|
+
Usage: erdraft <command> [options] [--database <id>]
|
|
226
|
+
erdraft --version
|
|
227
|
+
|
|
228
|
+
studio Open the window (the diagram, live)
|
|
229
|
+
mcp Serve the MCP on stdin and stdout, for an agent
|
|
230
|
+
read Read the schema again from the code
|
|
231
|
+
tables [--draft <id>] List the tables
|
|
232
|
+
table <name> [--draft <id>] Describe a table
|
|
233
|
+
drafts List the drafts
|
|
234
|
+
draft <id> Show a draft and what it changes
|
|
235
|
+
propose <file|-> --title <title> [--summary <text>] [--group <name>]
|
|
236
|
+
Propose a draft from a JSON array of operations
|
|
237
|
+
revise <id> [<file|->] [--replace] [--title <title>] [--summary <text>] [--group <name>]
|
|
238
|
+
Add operations to a draft (or put them in its place)
|
|
239
|
+
show [<table>] [--related] [--draft <id> | --schema]
|
|
240
|
+
Show it in the window: a table in focus, only it and the tables it joins, a draft
|
|
241
|
+
wait <id> [--seconds <n>] Wait until the person adopts a draft or sets it aside (30 s at most when not said)
|
|
242
|
+
adopt <id> Adopt a draft (the others of its group are set aside)
|
|
243
|
+
discard <id> Set a draft aside
|
|
244
|
+
delete <id> Delete a draft for good
|
|
245
|
+
databases List the project's databases, * the one worked on
|
|
246
|
+
install-skill [--force] Write Erdraft's skill into the project (.claude/skills/erdraft), for its agents
|
|
247
|
+
use <id> Work on another database (the window switches too)
|
|
248
|
+
|
|
249
|
+
A command works on the database chosen last (use), or on the one --database names. The nearest .erdraft.json says
|
|
250
|
+
where the databases' schemas and their drafts are.`;
|
|
251
|
+
async function operationsFrom(path, options) {
|
|
252
|
+
if (path === void 0) {
|
|
253
|
+
return void 0;
|
|
254
|
+
}
|
|
255
|
+
const text2 = path === "-" ? await options.stdin() : await readFile4(resolve2(options.cwd, path), "utf8");
|
|
256
|
+
return JSON.parse(text2);
|
|
257
|
+
}
|
|
258
|
+
function required(value, what) {
|
|
259
|
+
if (!value) {
|
|
260
|
+
throw new Error(`Missing ${what}. Run erdraft --help`);
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
async function command(argv, options) {
|
|
265
|
+
const { values, positionals } = parseArgs({
|
|
266
|
+
args: argv,
|
|
267
|
+
allowPositionals: true,
|
|
268
|
+
options: {
|
|
269
|
+
draft: { type: "string" },
|
|
270
|
+
title: { type: "string" },
|
|
271
|
+
summary: { type: "string" },
|
|
272
|
+
group: { type: "string" },
|
|
273
|
+
replace: { type: "boolean" },
|
|
274
|
+
related: { type: "boolean" },
|
|
275
|
+
schema: { type: "boolean" },
|
|
276
|
+
database: { type: "string" },
|
|
277
|
+
seconds: { type: "string" },
|
|
278
|
+
force: { type: "boolean" },
|
|
279
|
+
help: { type: "boolean", short: "h" },
|
|
280
|
+
version: { type: "boolean", short: "v" }
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
const [name, argument] = positionals;
|
|
284
|
+
if (values.version) {
|
|
285
|
+
options.out(version_default);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (!name || values.help) {
|
|
289
|
+
options.out(HELP);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const config = await loadConfig(options.cwd);
|
|
293
|
+
const database = values.database ? databaseOf(config, values.database) : await currentDatabase(config);
|
|
294
|
+
const drafts = storeOf(config, database);
|
|
295
|
+
async function base() {
|
|
296
|
+
return await drafts.readBase() ?? readSchema(config, database, drafts);
|
|
297
|
+
}
|
|
298
|
+
async function readOf(draft) {
|
|
299
|
+
return readDraft(
|
|
300
|
+
(await base()).schema,
|
|
301
|
+
draft ? (await drafts.draft(draft)).operations : []
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
async function schemaOf(draft) {
|
|
305
|
+
return (await readOf(draft)).diff;
|
|
306
|
+
}
|
|
307
|
+
switch (name) {
|
|
308
|
+
case "studio":
|
|
309
|
+
await readSchema(config, database, drafts);
|
|
310
|
+
return options.startStudio(config);
|
|
311
|
+
case "mcp":
|
|
312
|
+
return options.startMcp(config);
|
|
313
|
+
case "read": {
|
|
314
|
+
const base2 = await readSchema(config, database, drafts);
|
|
315
|
+
options.out(
|
|
316
|
+
`Read ${base2.schema.tables.length} tables and ${base2.schema.enums.length} enums from ${base2.source}.`
|
|
317
|
+
);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
case "tables":
|
|
321
|
+
options.out(describeTables(await schemaOf(values.draft)));
|
|
322
|
+
return;
|
|
323
|
+
case "table": {
|
|
324
|
+
const tableName = required(argument, "the table\u2019s name");
|
|
325
|
+
const diff = await schemaOf(values.draft);
|
|
326
|
+
const table = diff.tables.find((each) => each.name === tableName);
|
|
327
|
+
if (!table) {
|
|
328
|
+
throw new Error(`There is no table "${tableName}"`);
|
|
329
|
+
}
|
|
330
|
+
options.out(describeTable(table, diff));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
case "drafts": {
|
|
334
|
+
const all = await drafts.readDrafts();
|
|
335
|
+
options.out(
|
|
336
|
+
all.length > 0 ? all.map(draftLine).join("\n") : "No drafts yet."
|
|
337
|
+
);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
case "draft": {
|
|
341
|
+
const draft = await drafts.draft(required(argument, "the draft\u2019s id"));
|
|
342
|
+
const { diff, written } = await readOf(draft.id);
|
|
343
|
+
options.out(describeDraft(draft, diff, written));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
case "propose": {
|
|
347
|
+
await base();
|
|
348
|
+
const operations = await operationsFrom(
|
|
349
|
+
required(argument, "the operations\u2019 file"),
|
|
350
|
+
options
|
|
351
|
+
);
|
|
352
|
+
const draft = await drafts.propose({
|
|
353
|
+
title: required(values.title, "--title"),
|
|
354
|
+
summary: values.summary,
|
|
355
|
+
group: values.group,
|
|
356
|
+
operations
|
|
357
|
+
});
|
|
358
|
+
options.out(`Proposed ${draft.id}: ${draft.title}.`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
case "revise": {
|
|
362
|
+
await base();
|
|
363
|
+
const id = required(argument, "the draft\u2019s id");
|
|
364
|
+
const operations = await operationsFrom(positionals[2], options);
|
|
365
|
+
const draft = await drafts.revise(id, {
|
|
366
|
+
title: values.title,
|
|
367
|
+
summary: values.summary,
|
|
368
|
+
group: values.group,
|
|
369
|
+
mode: values.replace ? "replace" : "append",
|
|
370
|
+
operations
|
|
371
|
+
});
|
|
372
|
+
options.out(`Revised ${draft.id}: ${draft.title}.`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
case "show": {
|
|
376
|
+
const draft = values.schema ? null : values.draft;
|
|
377
|
+
const diff = await schemaOf(draft ?? void 0);
|
|
378
|
+
if (argument && !diff.tables.some((each) => each.name === argument)) {
|
|
379
|
+
throw new Error(`There is no table "${argument}"`);
|
|
380
|
+
}
|
|
381
|
+
await drafts.show({
|
|
382
|
+
table: argument ?? null,
|
|
383
|
+
related: values.related === true && argument !== void 0,
|
|
384
|
+
draft
|
|
385
|
+
});
|
|
386
|
+
options.out(`Shown in the window.`);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
case "wait": {
|
|
390
|
+
const seconds = Number(values.seconds ?? DECISION_SECONDS);
|
|
391
|
+
if (!Number.isInteger(seconds) || seconds < 1) {
|
|
392
|
+
throw new Error("--seconds is a whole number of seconds, 1 or more");
|
|
393
|
+
}
|
|
394
|
+
const draft = await drafts.decision(
|
|
395
|
+
required(argument, "the draft\u2019s id"),
|
|
396
|
+
seconds * 1e3
|
|
397
|
+
);
|
|
398
|
+
options.out(
|
|
399
|
+
describeDecision(
|
|
400
|
+
draft,
|
|
401
|
+
await drafts.readDrafts(),
|
|
402
|
+
seconds,
|
|
403
|
+
writtenIn((await base()).schema, draft.operations)
|
|
404
|
+
)
|
|
405
|
+
);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
case "adopt":
|
|
409
|
+
case "discard": {
|
|
410
|
+
const draft = await drafts.setStatus(
|
|
411
|
+
required(argument, "the draft\u2019s id"),
|
|
412
|
+
name === "adopt" ? "adopted" : "discarded"
|
|
413
|
+
);
|
|
414
|
+
options.out(
|
|
415
|
+
`${name === "adopt" ? "Adopted" : "Discarded"} ${draft.id}: ${draft.title}.`
|
|
416
|
+
);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
case "delete": {
|
|
420
|
+
const draft = await drafts.deleteDraft(
|
|
421
|
+
required(argument, "the draft\u2019s id")
|
|
422
|
+
);
|
|
423
|
+
options.out(`Deleted ${draft.id}: ${draft.title}.`);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
case "databases":
|
|
427
|
+
options.out(
|
|
428
|
+
config.databases.map(
|
|
429
|
+
(each) => `${each.id === database.id ? "*" : " "} ${each.id} ${each.name}`
|
|
430
|
+
).join("\n")
|
|
431
|
+
);
|
|
432
|
+
return;
|
|
433
|
+
case "install-skill": {
|
|
434
|
+
const { path, written } = await installSkill(
|
|
435
|
+
config.root,
|
|
436
|
+
options.skill,
|
|
437
|
+
values.force
|
|
438
|
+
);
|
|
439
|
+
options.out(
|
|
440
|
+
written ? `Wrote Erdraft's skill to ${path}.` : `Erdraft's skill is already in ${path}, the same.`
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
case "use": {
|
|
445
|
+
const chosen = await chooseDatabase(
|
|
446
|
+
config,
|
|
447
|
+
required(argument, "the database\u2019s id")
|
|
448
|
+
);
|
|
449
|
+
options.out(`Working on ${chosen.name}.`);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
default:
|
|
453
|
+
throw new Error(`Unknown command "${name}". Run erdraft --help`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
var ERROR_NAME = /^\w*Error(?: \[\w+\])?: /;
|
|
457
|
+
async function run(argv, options) {
|
|
458
|
+
try {
|
|
459
|
+
await command(argv, options);
|
|
460
|
+
return 0;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
options.err(
|
|
463
|
+
error instanceof z2.ZodError ? z2.prettifyError(error) : String(error).replace(ERROR_NAME, "")
|
|
464
|
+
);
|
|
465
|
+
return 1;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// bin/erdraft.ts
|
|
470
|
+
var WINDOW = fileURLToPath(new URL("./window", import.meta.url));
|
|
471
|
+
async function listen(config) {
|
|
472
|
+
if (existsSync(join4(WINDOW, "index.html"))) {
|
|
473
|
+
return serveWindow(config, WINDOW);
|
|
474
|
+
}
|
|
475
|
+
const [{ createServer: createServer3 }, { default: studioConfig }] = await Promise.all([
|
|
476
|
+
import("vite"),
|
|
477
|
+
import("./studio-config-TIVT4MNY.js")
|
|
478
|
+
]);
|
|
479
|
+
const server = await createServer3(studioConfig(config));
|
|
480
|
+
await server.listen();
|
|
481
|
+
return server.httpServer;
|
|
482
|
+
}
|
|
483
|
+
var code = await run(process.argv.slice(2), {
|
|
484
|
+
cwd: process.cwd(),
|
|
485
|
+
skill: fileURLToPath(new URL("../skill/SKILL.md", import.meta.url)),
|
|
486
|
+
out: (value) => process.stdout.write(`${value}
|
|
487
|
+
`),
|
|
488
|
+
err: (value) => process.stderr.write(`${value}
|
|
489
|
+
`),
|
|
490
|
+
stdin: () => text(process.stdin),
|
|
491
|
+
startMcp: async (config) => {
|
|
492
|
+
await createServer({ config }).connect(new StdioServerTransport());
|
|
493
|
+
await new Promise((done) => process.stdin.on("end", done));
|
|
494
|
+
},
|
|
495
|
+
startStudio: async (config) => {
|
|
496
|
+
const server = await listen(config);
|
|
497
|
+
process.stdout.write(
|
|
498
|
+
`Erdraft: ${config.name}'s data model at http://localhost:${config.port}
|
|
499
|
+
`
|
|
500
|
+
);
|
|
501
|
+
await new Promise((done) => server?.on("close", done));
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
process.stderr.write(
|
|
505
|
+
"",
|
|
506
|
+
() => process.stdout.write("", () => process.exit(code))
|
|
507
|
+
);
|