@graypirate/tabula 1.0.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.
Files changed (71) hide show
  1. package/README.md +141 -0
  2. package/dist/API/create.d.ts +33 -0
  3. package/dist/API/create.js +91 -0
  4. package/dist/API/delete.d.ts +31 -0
  5. package/dist/API/delete.js +38 -0
  6. package/dist/API/index.d.ts +13 -0
  7. package/dist/API/index.js +9 -0
  8. package/dist/API/init.d.ts +3 -0
  9. package/dist/API/init.js +8 -0
  10. package/dist/API/read.d.ts +60 -0
  11. package/dist/API/read.js +95 -0
  12. package/dist/API/search.d.ts +4 -0
  13. package/dist/API/search.js +4 -0
  14. package/dist/API/types.d.ts +23 -0
  15. package/dist/API/types.js +0 -0
  16. package/dist/API/validation.d.ts +8 -0
  17. package/dist/API/validation.js +103 -0
  18. package/dist/API/write.d.ts +30 -0
  19. package/dist/API/write.js +90 -0
  20. package/dist/CLI/arguments.d.ts +45 -0
  21. package/dist/CLI/arguments.js +263 -0
  22. package/dist/CLI/dispatch.d.ts +3 -0
  23. package/dist/CLI/dispatch.js +143 -0
  24. package/dist/CLI/errors.d.ts +10 -0
  25. package/dist/CLI/errors.js +20 -0
  26. package/dist/CLI/index.d.ts +6 -0
  27. package/dist/CLI/index.js +70 -0
  28. package/dist/CLI/io.d.ts +6 -0
  29. package/dist/CLI/io.js +28 -0
  30. package/dist/CLI/json.d.ts +9 -0
  31. package/dist/CLI/json.js +42 -0
  32. package/dist/CLI/properties.d.ts +3 -0
  33. package/dist/CLI/properties.js +22 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +1 -0
  36. package/dist/src/storage/db/blocks.d.ts +68 -0
  37. package/dist/src/storage/db/blocks.js +185 -0
  38. package/dist/src/storage/db/edges.d.ts +61 -0
  39. package/dist/src/storage/db/edges.js +306 -0
  40. package/dist/src/storage/db/init.d.ts +22 -0
  41. package/dist/src/storage/db/init.js +108 -0
  42. package/dist/src/storage/db/nodes.d.ts +34 -0
  43. package/dist/src/storage/db/nodes.js +91 -0
  44. package/dist/src/storage/db/objects.d.ts +55 -0
  45. package/dist/src/storage/db/objects.js +123 -0
  46. package/dist/src/storage/db/schema.sql +59 -0
  47. package/dist/src/storage/index.d.ts +47 -0
  48. package/dist/src/storage/index.js +315 -0
  49. package/dist/src/storage/types.d.ts +15 -0
  50. package/dist/src/storage/types.js +1 -0
  51. package/dist/src/types/block.d.ts +12 -0
  52. package/dist/src/types/block.js +0 -0
  53. package/dist/src/types/database.d.ts +6 -0
  54. package/dist/src/types/database.js +0 -0
  55. package/dist/src/types/graph.d.ts +11 -0
  56. package/dist/src/types/graph.js +0 -0
  57. package/dist/src/types/json.d.ts +7 -0
  58. package/dist/src/types/json.js +0 -0
  59. package/dist/src/types/object.d.ts +12 -0
  60. package/dist/src/types/object.js +0 -0
  61. package/dist/src/types/workspace.d.ts +6 -0
  62. package/dist/src/types/workspace.js +0 -0
  63. package/dist/src/utils/id.d.ts +6 -0
  64. package/dist/src/utils/id.js +18 -0
  65. package/dist/src/utils/yaml.d.ts +3 -0
  66. package/dist/src/utils/yaml.js +26 -0
  67. package/dist/src/workspace/index.d.ts +1 -0
  68. package/dist/src/workspace/index.js +1 -0
  69. package/dist/src/workspace/resolution.d.ts +20 -0
  70. package/dist/src/workspace/resolution.js +90 -0
  71. package/package.json +43 -0
@@ -0,0 +1,30 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import { type BlockResult, type Result, type ObjectResult, type BlockWrite, type ObjectWrite, type Write } from "./types";
3
+ type WriteOptions = {
4
+ parentID?: string;
5
+ };
6
+ /**
7
+ * Creates or replaces an object or block tree.
8
+ * @param db - The SQLite database backing the workspace
9
+ * @param input - The recursive entity input
10
+ * @param options - Optional parent placement for the root entity
11
+ * @returns The stored parent-aware recursive entity result
12
+ */
13
+ export declare function writeEntity(db: Database, input: Write, options?: WriteOptions): Result;
14
+ /**
15
+ * Creates or replaces a block tree.
16
+ * @param db - The SQLite database backing the workspace
17
+ * @param input - The recursive block input
18
+ * @param options - Optional parent placement for the root block
19
+ * @returns The stored recursive block tree
20
+ */
21
+ export declare function writeBlock(db: Database, input: BlockWrite, options?: WriteOptions): BlockResult;
22
+ /**
23
+ * Creates or replaces an object tree.
24
+ * @param db - The SQLite database backing the workspace
25
+ * @param input - The recursive object input
26
+ * @param options - Optional parent placement for the root object
27
+ * @returns The stored recursive object tree
28
+ */
29
+ export declare function writeObject(db: Database, input: ObjectWrite, options?: WriteOptions): ObjectResult;
30
+ export {};
@@ -0,0 +1,90 @@
1
+ import { entityExists, readEntityParentID, writeEntityTree, } from "../src/storage";
2
+ import { createBlockID, createObjID } from "../src/utils/id";
3
+ import {} from "./types";
4
+ /**
5
+ * Creates or replaces an object or block tree.
6
+ * @param db - The SQLite database backing the workspace
7
+ * @param input - The recursive entity input
8
+ * @param options - Optional parent placement for the root entity
9
+ * @returns The stored parent-aware recursive entity result
10
+ */
11
+ export function writeEntity(db, input, options = {}) {
12
+ const entity = prepareEntity(db, input);
13
+ const stored = writeEntityTree(db, entity, options.parentID ?? undefined);
14
+ return {
15
+ parentID: readEntityParentID(db, stored.id),
16
+ entity: stored,
17
+ };
18
+ }
19
+ /**
20
+ * Creates or replaces a block tree.
21
+ * @param db - The SQLite database backing the workspace
22
+ * @param input - The recursive block input
23
+ * @param options - Optional parent placement for the root block
24
+ * @returns The stored recursive block tree
25
+ */
26
+ export function writeBlock(db, input, options = {}) {
27
+ const result = writeEntity(db, input, options);
28
+ assertBlockResult(result);
29
+ return result;
30
+ }
31
+ /**
32
+ * Creates or replaces an object tree.
33
+ * @param db - The SQLite database backing the workspace
34
+ * @param input - The recursive object input
35
+ * @param options - Optional parent placement for the root object
36
+ * @returns The stored recursive object tree
37
+ */
38
+ export function writeObject(db, input, options = {}) {
39
+ const result = writeEntity(db, input, options);
40
+ assertObjectResult(result);
41
+ return result;
42
+ }
43
+ /** Assigns IDs while producing a recursive public entity tree. */
44
+ function prepareEntity(db, input) {
45
+ const usedIDs = new Set();
46
+ const visit = (entity) => {
47
+ const id = entity.id ?? createAvailableEntityID(db, entity.type, usedIDs);
48
+ if (usedIDs.has(id)) {
49
+ throw new Error(`Duplicate entity ID in write tree: ${id}`);
50
+ }
51
+ usedIDs.add(id);
52
+ const children = entity.children.map(visit);
53
+ if (entity.type === "object") {
54
+ return {
55
+ id,
56
+ type: "object",
57
+ name: entity.name,
58
+ properties: entity.properties ?? {},
59
+ children,
60
+ };
61
+ }
62
+ return {
63
+ id,
64
+ type: "block",
65
+ content: entity.content,
66
+ properties: entity.properties ?? {},
67
+ children,
68
+ };
69
+ };
70
+ return visit(input);
71
+ }
72
+ function assertObjectResult(result) {
73
+ if (result.entity.type !== "object") {
74
+ throw new Error("Expected object result");
75
+ }
76
+ }
77
+ function assertBlockResult(result) {
78
+ if (result.entity.type !== "block") {
79
+ throw new Error("Expected block result");
80
+ }
81
+ }
82
+ /** Generates an unused entity ID for the requested entity type. */
83
+ function createAvailableEntityID(db, type, reserved) {
84
+ const createID = type === "object" ? createObjID : createBlockID;
85
+ let id = createID();
86
+ while (reserved.has(id) || entityExists(db, id)) {
87
+ id = createID();
88
+ }
89
+ return id;
90
+ }
@@ -0,0 +1,45 @@
1
+ export type EntityType = "workspace" | "object" | "block";
2
+ export type SearchType = Exclude<EntityType, "workspace">;
3
+ export type CLICommand = {
4
+ action: "init";
5
+ workspace: string;
6
+ } | {
7
+ action: "create";
8
+ entity: "object";
9
+ workspace: string;
10
+ name: string;
11
+ propertyValues: string[];
12
+ parentID?: string;
13
+ } | {
14
+ action: "create";
15
+ entity: "block";
16
+ workspace: string;
17
+ content: string;
18
+ propertyValues: string[];
19
+ parentID: string;
20
+ } | {
21
+ action: "write";
22
+ workspace: string;
23
+ parentID?: string;
24
+ } | {
25
+ action: "read";
26
+ workspace: string;
27
+ id?: string;
28
+ } | {
29
+ action: "listWorkspaces";
30
+ } | {
31
+ action: "list";
32
+ workspace: string;
33
+ id?: string;
34
+ } | {
35
+ action: "delete";
36
+ workspace: string;
37
+ id?: string;
38
+ } | {
39
+ action: "search";
40
+ workspace: string;
41
+ query: string;
42
+ type?: SearchType;
43
+ };
44
+ export declare function parseCommand(argv: string[]): CLICommand;
45
+ export declare function inferEntityType(id: string): EntityType;
@@ -0,0 +1,263 @@
1
+ import { InvalidWorkspaceNameError, validateWorkspaceName, } from "../index.js";
2
+ import { CLIInputError } from "./errors.js";
3
+ const optionNames = new Set([
4
+ "content",
5
+ "name",
6
+ "parent",
7
+ "property",
8
+ "type",
9
+ "workspace",
10
+ ]);
11
+ export function parseCommand(argv) {
12
+ const parsed = parseArguments(argv);
13
+ const action = parsed.positionals[0];
14
+ if (action === undefined) {
15
+ throw inputError("INVALID_COMMAND", "Command is required");
16
+ }
17
+ switch (action) {
18
+ case "list": {
19
+ if (parsed.positionals.length === 1 && !parsed.options.has("workspace")) {
20
+ allowOptions(parsed, []);
21
+ return { action: "listWorkspaces" };
22
+ }
23
+ const workspace = requireCommandWorkspace(parsed);
24
+ if (parsed.positionals.length > 2) {
25
+ throw inputError("INVALID_ARGUMENTS", "Usage: tabula list [ID] --workspace NAME");
26
+ }
27
+ allowOptions(parsed, ["workspace"]);
28
+ const id = parsed.positionals[1];
29
+ if (id !== undefined) {
30
+ inferEntityType(id);
31
+ }
32
+ return id === undefined
33
+ ? { action, workspace }
34
+ : { action, workspace, id };
35
+ }
36
+ case "init": {
37
+ const workspace = requireCommandWorkspace(parsed);
38
+ requirePositionals(parsed, 1, "tabula init --workspace NAME");
39
+ allowOptions(parsed, ["workspace"]);
40
+ return {
41
+ action,
42
+ workspace,
43
+ };
44
+ }
45
+ case "create": {
46
+ const workspace = requireCommandWorkspace(parsed);
47
+ return parseCreate(parsed, workspace);
48
+ }
49
+ case "write": {
50
+ const workspace = requireCommandWorkspace(parsed);
51
+ requirePositionals(parsed, 1, "tabula write --workspace NAME [--parent ID] < entity.json");
52
+ allowOptions(parsed, ["workspace", "parent"]);
53
+ const parentID = optionalParentID(parsed);
54
+ return parentID === undefined
55
+ ? { action, workspace }
56
+ : { action, workspace, parentID };
57
+ }
58
+ case "read": {
59
+ const workspace = requireCommandWorkspace(parsed);
60
+ if (parsed.positionals.length > 2) {
61
+ throw inputError("INVALID_ARGUMENTS", "Usage: tabula read [ID] --workspace NAME");
62
+ }
63
+ allowOptions(parsed, ["workspace"]);
64
+ const id = parsed.positionals[1];
65
+ if (id !== undefined) {
66
+ inferEntityType(id);
67
+ }
68
+ return id === undefined
69
+ ? { action, workspace }
70
+ : { action, workspace, id };
71
+ }
72
+ case "delete": {
73
+ const workspace = requireCommandWorkspace(parsed);
74
+ if (parsed.positionals.length > 2) {
75
+ throw inputError("INVALID_ARGUMENTS", "Usage: tabula delete [ID] --workspace NAME");
76
+ }
77
+ allowOptions(parsed, ["workspace"]);
78
+ const id = parsed.positionals[1];
79
+ if (id === undefined) {
80
+ return { action, workspace };
81
+ }
82
+ if (inferEntityType(id) === "workspace") {
83
+ throw inputError("UNSUPPORTED_DELETE", "Delete workspaces by name: tabula delete --workspace NAME");
84
+ }
85
+ return { action, workspace, id };
86
+ }
87
+ case "search": {
88
+ const workspace = requireCommandWorkspace(parsed);
89
+ requirePositionals(parsed, 2, "tabula search QUERY --workspace NAME [--type object|block]");
90
+ allowOptions(parsed, ["workspace", "type"]);
91
+ const type = optionalSingleOption(parsed, "type");
92
+ if (type !== undefined && !isSearchType(type)) {
93
+ throw inputError("INVALID_SEARCH_TYPE", `Invalid search type: ${type}`, { allowed: ["object", "block"] });
94
+ }
95
+ return {
96
+ action,
97
+ workspace,
98
+ query: parsed.positionals[1],
99
+ ...(type === undefined ? {} : { type }),
100
+ };
101
+ }
102
+ default:
103
+ throw inputError("INVALID_COMMAND", `Unknown command: ${action}`);
104
+ }
105
+ }
106
+ export function inferEntityType(id) {
107
+ switch (id.slice(0, 2)) {
108
+ case "d_":
109
+ return "workspace";
110
+ case "o_":
111
+ return "object";
112
+ case "b_":
113
+ return "block";
114
+ default:
115
+ throw inputError("INVALID_ID", `Unknown entity ID prefix: ${id}`);
116
+ }
117
+ }
118
+ function parseCreate(parsed, workspace) {
119
+ requirePositionals(parsed, 2, "tabula create object|block --workspace NAME [options]");
120
+ const entity = parsed.positionals[1];
121
+ switch (entity) {
122
+ case "object": {
123
+ allowOptions(parsed, ["workspace", "name", "parent", "property"]);
124
+ const parentID = optionalCreateParentID(parsed, entity);
125
+ return {
126
+ action: "create",
127
+ entity,
128
+ workspace,
129
+ name: requireSingleOption(parsed, "name"),
130
+ propertyValues: parsed.options.get("property") ?? [],
131
+ ...(parentID === undefined ? {} : { parentID }),
132
+ };
133
+ }
134
+ case "block": {
135
+ allowOptions(parsed, ["workspace", "content", "parent", "property"]);
136
+ const parentID = requireCreateParentID(parsed, entity);
137
+ return {
138
+ action: "create",
139
+ entity,
140
+ workspace,
141
+ content: requireSingleOption(parsed, "content"),
142
+ propertyValues: parsed.options.get("property") ?? [],
143
+ parentID,
144
+ };
145
+ }
146
+ default:
147
+ throw inputError("INVALID_ENTITY_TYPE", `Unknown create entity type: ${entity ?? ""}`, { allowed: ["object", "block"] });
148
+ }
149
+ }
150
+ function parseArguments(argv) {
151
+ const options = new Map();
152
+ const positionals = [];
153
+ for (let index = 0; index < argv.length; index += 1) {
154
+ const argument = argv[index];
155
+ if (!argument.startsWith("-")) {
156
+ positionals.push(argument);
157
+ continue;
158
+ }
159
+ if (!argument.startsWith("--")) {
160
+ throw inputError("UNKNOWN_OPTION", `Only long options are supported: ${argument}`);
161
+ }
162
+ const separator = argument.indexOf("=");
163
+ const name = argument.slice(2, separator === -1 ? undefined : separator);
164
+ if (!optionNames.has(name)) {
165
+ throw inputError("UNKNOWN_OPTION", `Unknown option: --${name}`);
166
+ }
167
+ const inlineValue = separator === -1 ? undefined : argument.slice(separator + 1);
168
+ const nextValue = argv[index + 1];
169
+ const value = inlineValue ?? nextValue;
170
+ if (value === undefined
171
+ || value.length === 0
172
+ || (inlineValue === undefined && value.startsWith("--"))) {
173
+ throw inputError("MISSING_OPTION_VALUE", `Option --${name} requires a value`);
174
+ }
175
+ if (inlineValue === undefined) {
176
+ index += 1;
177
+ }
178
+ const values = options.get(name) ?? [];
179
+ values.push(value);
180
+ options.set(name, values);
181
+ }
182
+ return { options, positionals };
183
+ }
184
+ function validateCommandWorkspaceName(workspace) {
185
+ try {
186
+ validateWorkspaceName(workspace);
187
+ }
188
+ catch (error) {
189
+ if (error instanceof InvalidWorkspaceNameError) {
190
+ throw inputError("INVALID_WORKSPACE_NAME", error.message, error.details);
191
+ }
192
+ throw error;
193
+ }
194
+ }
195
+ function requireCommandWorkspace(parsed) {
196
+ const workspace = requireSingleOption(parsed, "workspace");
197
+ validateCommandWorkspaceName(workspace);
198
+ return workspace;
199
+ }
200
+ function allowOptions(parsed, allowed) {
201
+ const allowedOptions = new Set(allowed);
202
+ for (const name of parsed.options.keys()) {
203
+ if (!allowedOptions.has(name)) {
204
+ throw inputError("INVALID_OPTION", `Option --${name} is not valid for this command`);
205
+ }
206
+ }
207
+ }
208
+ function requirePositionals(parsed, count, usage) {
209
+ if (parsed.positionals.length !== count) {
210
+ throw inputError("INVALID_ARGUMENTS", `Usage: ${usage}`);
211
+ }
212
+ }
213
+ function requireSingleOption(parsed, name) {
214
+ const value = optionalSingleOption(parsed, name);
215
+ if (value === undefined) {
216
+ throw inputError("MISSING_OPTION", `Required option missing: --${name}`);
217
+ }
218
+ return value;
219
+ }
220
+ function optionalSingleOption(parsed, name) {
221
+ const values = parsed.options.get(name);
222
+ if (values === undefined) {
223
+ return undefined;
224
+ }
225
+ if (values.length !== 1) {
226
+ throw inputError("DUPLICATE_OPTION", `Option --${name} may only be specified once`);
227
+ }
228
+ return values[0];
229
+ }
230
+ function optionalCreateParentID(parsed, childType) {
231
+ const parentID = optionalParentID(parsed);
232
+ if (parentID === undefined) {
233
+ return undefined;
234
+ }
235
+ validateParentType(parentID, childType);
236
+ return parentID;
237
+ }
238
+ function requireCreateParentID(parsed, childType) {
239
+ const parentID = optionalParentID(parsed);
240
+ if (parentID === undefined) {
241
+ throw inputError("MISSING_OPTION", "Required option missing: --parent");
242
+ }
243
+ validateParentType(parentID, childType);
244
+ return parentID;
245
+ }
246
+ function optionalParentID(parsed) {
247
+ const parentID = optionalSingleOption(parsed, "parent");
248
+ if (parentID !== undefined) {
249
+ inferEntityType(parentID);
250
+ }
251
+ return parentID;
252
+ }
253
+ function validateParentType(parentID, childType) {
254
+ if (childType === "block" && inferEntityType(parentID) === "workspace") {
255
+ throw inputError("INVALID_PARENT", `Workspace parents can only contain objects: ${parentID}`);
256
+ }
257
+ }
258
+ function isSearchType(value) {
259
+ return value === "object" || value === "block";
260
+ }
261
+ function inputError(code, message, details) {
262
+ return new CLIInputError(code, message, details);
263
+ }
@@ -0,0 +1,3 @@
1
+ import { type CLICommand } from "./arguments.js";
2
+ import type { WriteInput } from "./json.js";
3
+ export declare function dispatchCommand(command: CLICommand, writeInput?: WriteInput): unknown;
@@ -0,0 +1,143 @@
1
+ import { create, deleteEntity as deleteAPIEntity, deleteWorkspace, initializeWorkspace, listEntity as listAPIEntity, listWorkspaceNames, listWorkspace, openWorkspace, readEntity as readAPIEntity, readWorkspace, search, writeEntity, } from "../index.js";
2
+ import { inferEntityType } from "./arguments.js";
3
+ import { CLIInputError, CLIOperationError } from "./errors.js";
4
+ import { parseProperties } from "./properties.js";
5
+ export function dispatchCommand(command, writeInput) {
6
+ if (command.action === "init") {
7
+ return withInitializedWorkspace(command.workspace, readWorkspace);
8
+ }
9
+ if (command.action === "listWorkspaces") {
10
+ return listWorkspaceNames();
11
+ }
12
+ if (command.action === "delete" && command.id === undefined) {
13
+ try {
14
+ return deleteWorkspace(command.workspace);
15
+ }
16
+ catch (error) {
17
+ throw operationError("WORKSPACE_DELETE_FAILED", error, {
18
+ workspace: command.workspace,
19
+ });
20
+ }
21
+ }
22
+ const properties = command.action === "create"
23
+ ? parseProperties(command.propertyValues)
24
+ : {};
25
+ let db;
26
+ try {
27
+ db = openWorkspace(command.workspace);
28
+ }
29
+ catch (error) {
30
+ throw operationError("WORKSPACE_OPEN_FAILED", error, {
31
+ workspace: command.workspace,
32
+ });
33
+ }
34
+ try {
35
+ switch (command.action) {
36
+ case "create": {
37
+ switch (command.entity) {
38
+ case "object":
39
+ return create(db, {
40
+ type: "object",
41
+ name: command.name,
42
+ properties,
43
+ }, createOptions(command.parentID));
44
+ case "block":
45
+ return create(db, {
46
+ type: "block",
47
+ content: command.content,
48
+ properties,
49
+ }, createOptions(command.parentID));
50
+ }
51
+ }
52
+ case "write":
53
+ if (writeInput === undefined) {
54
+ throw new Error("Validated write input is required");
55
+ }
56
+ if (writeInput.entity === "block" && command.parentID === undefined) {
57
+ throw new CLIInputError("MISSING_OPTION", "Required option missing: --parent");
58
+ }
59
+ return writeEntity(db, writeInput.value, createOptions(command.parentID));
60
+ case "read":
61
+ return command.id === undefined
62
+ ? readWorkspace(db)
63
+ : readCommandEntity(db, command.id);
64
+ case "list":
65
+ return command.id === undefined
66
+ ? listWorkspace(db)
67
+ : listCommandEntity(db, command.id);
68
+ case "delete":
69
+ if (command.id === undefined) {
70
+ throw new Error("Validated delete entity ID is required");
71
+ }
72
+ return deleteCommandEntity(db, command.id);
73
+ case "search":
74
+ return search(db, command.query, command.type);
75
+ }
76
+ }
77
+ catch (error) {
78
+ if (error instanceof CLIInputError || error instanceof CLIOperationError) {
79
+ throw error;
80
+ }
81
+ throw operationError("OPERATION_FAILED", error);
82
+ }
83
+ finally {
84
+ db.close();
85
+ }
86
+ }
87
+ function withInitializedWorkspace(name, read) {
88
+ let db;
89
+ try {
90
+ db = initializeWorkspace(name);
91
+ }
92
+ catch (error) {
93
+ throw operationError("WORKSPACE_INIT_FAILED", error, { workspace: name });
94
+ }
95
+ try {
96
+ return read(db);
97
+ }
98
+ finally {
99
+ db.close();
100
+ }
101
+ }
102
+ function readCommandEntity(db, id) {
103
+ switch (inferEntityType(id)) {
104
+ case "workspace":
105
+ return readMatchingWorkspace(db, id);
106
+ case "object":
107
+ case "block":
108
+ return readAPIEntity(db, id);
109
+ }
110
+ }
111
+ function listCommandEntity(db, id) {
112
+ switch (inferEntityType(id)) {
113
+ case "workspace":
114
+ readMatchingWorkspace(db, id);
115
+ return listWorkspace(db);
116
+ case "object":
117
+ case "block":
118
+ return listAPIEntity(db, id);
119
+ }
120
+ }
121
+ function deleteCommandEntity(db, id) {
122
+ switch (inferEntityType(id)) {
123
+ case "workspace":
124
+ throw new CLIOperationError("UNSUPPORTED_DELETE", "Workspace deletion is not supported");
125
+ case "object":
126
+ case "block":
127
+ return deleteAPIEntity(db, id);
128
+ }
129
+ }
130
+ function createOptions(parentID) {
131
+ return parentID === undefined ? {} : { parentID };
132
+ }
133
+ function readMatchingWorkspace(db, id) {
134
+ const metadata = readWorkspace(db);
135
+ if (metadata.id !== id) {
136
+ throw new CLIOperationError("WORKSPACE_ID_MISMATCH", `Workspace ID ${id} does not match the opened workspace`, { actualID: metadata.id });
137
+ }
138
+ return metadata;
139
+ }
140
+ function operationError(code, error, details) {
141
+ const message = error instanceof Error ? error.message : String(error);
142
+ return new CLIOperationError(code, message, details);
143
+ }
@@ -0,0 +1,10 @@
1
+ export declare class CLIInputError extends Error {
2
+ readonly code: string;
3
+ readonly details?: unknown;
4
+ constructor(code: string, message: string, details?: unknown);
5
+ }
6
+ export declare class CLIOperationError extends Error {
7
+ readonly code: string;
8
+ readonly details?: unknown;
9
+ constructor(code: string, message: string, details?: unknown);
10
+ }
@@ -0,0 +1,20 @@
1
+ export class CLIInputError extends Error {
2
+ code;
3
+ details;
4
+ constructor(code, message, details) {
5
+ super(message);
6
+ this.name = "CLIInputError";
7
+ this.code = code;
8
+ this.details = details;
9
+ }
10
+ }
11
+ export class CLIOperationError extends Error {
12
+ code;
13
+ details;
14
+ constructor(code, message, details) {
15
+ super(message);
16
+ this.name = "CLIOperationError";
17
+ this.code = code;
18
+ this.details = details;
19
+ }
20
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ export declare function runCLI(argv: string[], read?: () => Promise<string>, isTTY?: boolean | undefined, confirm?: (prompt: string) => Promise<boolean>): Promise<{
3
+ exitCode: number;
4
+ output: unknown;
5
+ stream: "stdout" | "stderr";
6
+ }>;
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bun
2
+ import { parseCommand } from "./arguments.js";
3
+ import { dispatchCommand } from "./dispatch.js";
4
+ import { CLIInputError, CLIOperationError } from "./errors.js";
5
+ import { confirmationDialogue, readStdin } from "./io.js";
6
+ import { parseWriteInput } from "./json.js";
7
+ export async function runCLI(argv, read = () => Bun.stdin.text(), isTTY = process.stdin.isTTY, confirm = (prompt) => confirmationDialogue(prompt, { isTTY })) {
8
+ try {
9
+ const command = parseCommand(argv);
10
+ const input = await readStdin(command.action === "write", isTTY, read);
11
+ const writeInput = command.action === "write" ? parseWriteInput(input) : undefined;
12
+ if (command.action === "delete") {
13
+ const target = command.id ?? command.workspace;
14
+ if (!await confirm(`Delete ${target}?`)) {
15
+ return success(false);
16
+ }
17
+ }
18
+ return success(dispatchCommand(command, writeInput));
19
+ }
20
+ catch (error) {
21
+ return failure(error);
22
+ }
23
+ }
24
+ function success(output) {
25
+ return { exitCode: 0, output, stream: "stdout" };
26
+ }
27
+ function failure(error) {
28
+ if (error instanceof CLIInputError) {
29
+ return {
30
+ exitCode: 2,
31
+ output: errorOutput(error.code, error.message, error.details),
32
+ stream: "stderr",
33
+ };
34
+ }
35
+ if (error instanceof CLIOperationError) {
36
+ return {
37
+ exitCode: 1,
38
+ output: errorOutput(error.code, error.message, error.details),
39
+ stream: "stderr",
40
+ };
41
+ }
42
+ return {
43
+ exitCode: 1,
44
+ output: errorOutput("INTERNAL_ERROR", error instanceof Error ? error.message : String(error)),
45
+ stream: "stderr",
46
+ };
47
+ }
48
+ function errorOutput(code, message, details) {
49
+ return {
50
+ error: {
51
+ code,
52
+ message,
53
+ ...(details === undefined ? {} : { details }),
54
+ },
55
+ };
56
+ }
57
+ async function main() {
58
+ const result = await runCLI(Bun.argv.slice(2));
59
+ const output = `${JSON.stringify(result.output)}\n`;
60
+ if (result.stream === "stdout") {
61
+ await Bun.write(Bun.stdout, output);
62
+ }
63
+ else {
64
+ await Bun.write(Bun.stderr, output);
65
+ }
66
+ process.exitCode = result.exitCode;
67
+ }
68
+ if (import.meta.main) {
69
+ await main();
70
+ }