openwork-server 0.1.1 → 0.11.38

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Different AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -21,6 +21,8 @@ pnpm --filter openwork-server dev -- \
21
21
 
22
22
  The server logs the client token and host token on boot when they are auto-generated.
23
23
 
24
+ Add `--verbose` to print resolved config details on startup. Use `--version` to print the server version and exit.
25
+
24
26
  ## Config file
25
27
 
26
28
  Defaults to `~/.config/openwork/server.json` (override with `OPENWORK_SERVER_CONFIG` or `--config`).
@@ -58,13 +60,32 @@ Defaults to `~/.config/openwork/server.json` (override with `OPENWORK_SERVER_CON
58
60
  - `OPENWORK_OPENCODE_USERNAME`
59
61
  - `OPENWORK_OPENCODE_PASSWORD`
60
62
 
61
- ## Endpoints (initial)
63
+ Token management (scoped tokens):
64
+
65
+ - `OPENWORK_TOKEN_STORE` path to token store JSON (default: alongside `server.json`)
66
+
67
+ File injection / artifacts:
68
+
69
+ - `OPENWORK_INBOX_ENABLED` (`1` | `0`)
70
+ - `OPENWORK_INBOX_MAX_BYTES` (default: 50MB, capped)
71
+ - `OPENWORK_OUTBOX_ENABLED` (`1` | `0`)
72
+
73
+ Sandbox advertisement (for capability discovery):
74
+
75
+ - `OPENWORK_SANDBOX_ENABLED` (`1` | `0`)
76
+ - `OPENWORK_SANDBOX_BACKEND` (`docker` | `container` | `none`)
77
+
78
+ ## Endpoints
62
79
 
63
80
  - `GET /health`
81
+ - `GET /status`
64
82
  - `GET /capabilities`
83
+ - `GET /whoami`
65
84
  - `GET /workspaces`
66
85
  - `GET /workspace/:id/config`
67
86
  - `PATCH /workspace/:id/config`
87
+ - `GET /workspace/:id/events`
88
+ - `POST /workspace/:id/engine/reload`
68
89
  - `GET /workspace/:id/plugins`
69
90
  - `POST /workspace/:id/plugins`
70
91
  - `DELETE /workspace/:id/plugins/:name`
@@ -80,9 +101,48 @@ Defaults to `~/.config/openwork/server.json` (override with `OPENWORK_SERVER_CON
80
101
  - `GET /workspace/:id/export`
81
102
  - `POST /workspace/:id/import`
82
103
 
104
+ Token management (host/owner auth):
105
+
106
+ - `GET /tokens`
107
+ - `POST /tokens` (body: `{ "scope": "owner"|"collaborator"|"viewer", "label"?: string }`)
108
+ - `DELETE /tokens/:id`
109
+
110
+ Inbox/outbox:
111
+
112
+ - `POST /workspace/:id/inbox` (multipart upload into `.opencode/openwork/inbox/`)
113
+ - `GET /workspace/:id/artifacts`
114
+ - `GET /workspace/:id/artifacts/:artifactId`
115
+
116
+ Toy UI (static assets served by the server):
117
+
118
+ - `GET /ui`
119
+ - `GET /w/:id/ui`
120
+ - `GET /ui/assets/*`
121
+
122
+ OpenCode proxy:
123
+
124
+ - `GET|POST|... /opencode/*`
125
+ - `GET|POST|... /w/:id/opencode/*`
126
+
127
+ Owpenbot proxy:
128
+
129
+ - `GET|POST|... /owpenbot/*`
130
+ - `GET|POST|... /w/:id/owpenbot/*`
131
+
132
+ Auth policy:
133
+ - `GET /owpenbot/health` requires client auth.
134
+ - All other `/owpenbot/*` endpoints require host/owner auth.
135
+
83
136
  ## Approvals
84
137
 
85
- All writes are gated by host approval. Host APIs require `X-OpenWork-Host-Token`:
138
+ All writes are gated by host approval.
139
+
140
+ Host APIs accept either:
141
+
142
+ - `X-OpenWork-Host-Token: <token>` (legacy host token), or
143
+ - `Authorization: Bearer <token>` where the token scope is `owner`.
144
+
145
+ Approvals endpoints:
86
146
 
87
147
  - `GET /approvals`
88
148
  - `POST /approvals/:id` with `{ "reply": "allow" | "deny" }`
Binary file
package/package.json CHANGED
@@ -1,20 +1,11 @@
1
1
  {
2
2
  "name": "openwork-server",
3
- "version": "0.1.1",
3
+ "version": "0.11.38",
4
4
  "description": "Filesystem-backed API for OpenWork remote clients",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "openwork-server": "dist/bin/openwork-server"
8
8
  },
9
- "scripts": {
10
- "dev": "bun src/cli.ts",
11
- "build": "tsc -p tsconfig.json",
12
- "build:bin": "bun build --compile src/cli.ts --outfile dist/bin/openwork-server",
13
- "build:bin:all": "bun ./script/build.ts --outdir dist/bin --target bun-darwin-arm64 --target bun-darwin-x64 --target bun-linux-x64 --target bun-windows-x64",
14
- "start": "bun dist/cli.js",
15
- "typecheck": "tsc -p tsconfig.json --noEmit",
16
- "prepublishOnly": "pnpm build:bin"
17
- },
18
9
  "files": [
19
10
  "dist",
20
11
  "README.md"
@@ -50,5 +41,12 @@
50
41
  "bun-types": "^1.3.6",
51
42
  "typescript": "^5.6.3"
52
43
  },
53
- "packageManager": "pnpm@10.27.0"
54
- }
44
+ "scripts": {
45
+ "dev": "bun src/cli.ts",
46
+ "build": "tsc -p tsconfig.json",
47
+ "build:bin": "bun build --compile src/cli.ts --outfile dist/bin/openwork-server",
48
+ "build:bin:all": "bun ./script/build.ts --outdir dist/bin --target bun-darwin-arm64 --target bun-darwin-x64 --target bun-linux-x64 --target bun-linux-arm64 --target bun-windows-x64",
49
+ "start": "bun dist/cli.js",
50
+ "typecheck": "tsc -p tsconfig.json --noEmit"
51
+ }
52
+ }
package/dist/approvals.js DELETED
@@ -1,45 +0,0 @@
1
- import { shortId } from "./utils.js";
2
- export class ApprovalService {
3
- config;
4
- pending = new Map();
5
- constructor(config) {
6
- this.config = config;
7
- }
8
- list() {
9
- return Array.from(this.pending.values()).map((entry) => entry.request);
10
- }
11
- async requestApproval(input) {
12
- if (this.config.mode === "auto") {
13
- return { id: "auto", allowed: true };
14
- }
15
- const id = shortId();
16
- const request = {
17
- ...input,
18
- id,
19
- createdAt: Date.now(),
20
- };
21
- const result = await new Promise((resolve) => {
22
- const timeout = setTimeout(() => {
23
- this.pending.delete(id);
24
- resolve({ id, allowed: false, reason: "timeout" });
25
- }, this.config.timeoutMs);
26
- this.pending.set(id, { request, resolve, timeout });
27
- });
28
- return result;
29
- }
30
- respond(id, reply) {
31
- const pending = this.pending.get(id);
32
- if (!pending)
33
- return null;
34
- if (pending.timeout)
35
- clearTimeout(pending.timeout);
36
- this.pending.delete(id);
37
- const result = {
38
- id,
39
- allowed: reply === "allow",
40
- reason: reply === "allow" ? undefined : "denied",
41
- };
42
- pending.resolve(result);
43
- return result;
44
- }
45
- }
package/dist/audit.js DELETED
@@ -1,47 +0,0 @@
1
- import { dirname, join } from "node:path";
2
- import { appendFile, readFile } from "node:fs/promises";
3
- import { ensureDir, exists } from "./utils.js";
4
- export function auditLogPath(workspaceRoot) {
5
- return join(workspaceRoot, ".opencode", "openwork", "audit.jsonl");
6
- }
7
- export async function recordAudit(workspaceRoot, entry) {
8
- const path = auditLogPath(workspaceRoot);
9
- await ensureDir(dirname(path));
10
- await appendFile(path, JSON.stringify(entry) + "\n", "utf8");
11
- }
12
- export async function readLastAudit(workspaceRoot) {
13
- const path = auditLogPath(workspaceRoot);
14
- if (!(await exists(path)))
15
- return null;
16
- const content = await readFile(path, "utf8");
17
- const lines = content.trim().split("\n");
18
- const last = lines[lines.length - 1];
19
- if (!last)
20
- return null;
21
- try {
22
- return JSON.parse(last);
23
- }
24
- catch {
25
- return null;
26
- }
27
- }
28
- export async function readAuditEntries(workspaceRoot, limit = 50) {
29
- const path = auditLogPath(workspaceRoot);
30
- if (!(await exists(path)))
31
- return [];
32
- const content = await readFile(path, "utf8");
33
- const rawLines = content.trim().split("\n").filter(Boolean);
34
- if (!rawLines.length)
35
- return [];
36
- const slice = rawLines.slice(-Math.max(1, limit));
37
- const entries = [];
38
- for (let i = slice.length - 1; i >= 0; i -= 1) {
39
- try {
40
- entries.push(JSON.parse(slice[i]));
41
- }
42
- catch {
43
- // ignore malformed entry
44
- }
45
- }
46
- return entries;
47
- }
package/dist/cli.js DELETED
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { parseCliArgs, printHelp, resolveServerConfig } from "./config.js";
3
- import { startServer } from "./server.js";
4
- const args = parseCliArgs(process.argv.slice(2));
5
- if (args.help) {
6
- printHelp();
7
- process.exit(0);
8
- }
9
- const config = await resolveServerConfig(args);
10
- const server = startServer(config);
11
- const url = `http://${config.host}:${server.port}`;
12
- console.log(`OpenWork server listening on ${url}`);
13
- if (config.tokenSource === "generated") {
14
- console.log(`Client token: ${config.token}`);
15
- }
16
- if (config.hostTokenSource === "generated") {
17
- console.log(`Host token: ${config.hostToken}`);
18
- }
19
- if (config.workspaces.length === 0) {
20
- console.log("No workspaces configured. Add --workspace or update server.json.");
21
- }
22
- else {
23
- console.log(`Workspaces: ${config.workspaces.length}`);
24
- }
package/dist/commands.js DELETED
@@ -1,73 +0,0 @@
1
- import { readdir, readFile, writeFile, rm, mkdir } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { homedir } from "node:os";
4
- import { parseFrontmatter, buildFrontmatter } from "./frontmatter.js";
5
- import { exists } from "./utils.js";
6
- import { projectCommandsDir } from "./workspace-files.js";
7
- import { validateCommandName, sanitizeCommandName } from "./validators.js";
8
- import { ApiError } from "./errors.js";
9
- async function listCommandsInDir(dir, scope) {
10
- if (!(await exists(dir)))
11
- return [];
12
- const entries = await readdir(dir, { withFileTypes: true });
13
- const items = [];
14
- for (const entry of entries) {
15
- if (!entry.isFile())
16
- continue;
17
- if (!entry.name.endsWith(".md"))
18
- continue;
19
- const filePath = join(dir, entry.name);
20
- const content = await readFile(filePath, "utf8");
21
- const { data, body } = parseFrontmatter(content);
22
- const name = typeof data.name === "string" ? data.name : entry.name.replace(/\.md$/, "");
23
- try {
24
- validateCommandName(name);
25
- }
26
- catch {
27
- continue;
28
- }
29
- items.push({
30
- name,
31
- description: typeof data.description === "string" ? data.description : undefined,
32
- template: body.trim(),
33
- agent: typeof data.agent === "string" ? data.agent : undefined,
34
- model: typeof data.model === "string" ? data.model : null,
35
- subtask: typeof data.subtask === "boolean" ? data.subtask : undefined,
36
- scope,
37
- });
38
- }
39
- return items;
40
- }
41
- export async function listCommands(workspaceRoot, scope) {
42
- if (scope === "global") {
43
- const dir = join(homedir(), ".config", "opencode", "commands");
44
- return listCommandsInDir(dir, "global");
45
- }
46
- return listCommandsInDir(projectCommandsDir(workspaceRoot), "workspace");
47
- }
48
- export async function upsertCommand(workspaceRoot, payload) {
49
- if (!payload.template || payload.template.trim().length === 0) {
50
- throw new ApiError(400, "invalid_command_template", "Command template is required");
51
- }
52
- const sanitized = sanitizeCommandName(payload.name);
53
- validateCommandName(sanitized);
54
- const frontmatter = buildFrontmatter({
55
- name: sanitized,
56
- description: payload.description,
57
- agent: payload.agent,
58
- model: payload.model ?? null,
59
- subtask: payload.subtask ?? false,
60
- });
61
- const content = frontmatter + "\n" + payload.template.trim() + "\n";
62
- const dir = projectCommandsDir(workspaceRoot);
63
- await mkdir(dir, { recursive: true });
64
- const path = join(dir, `${sanitized}.md`);
65
- await writeFile(path, content, "utf8");
66
- return path;
67
- }
68
- export async function deleteCommand(workspaceRoot, name) {
69
- const sanitized = sanitizeCommandName(name);
70
- validateCommandName(sanitized);
71
- const path = join(projectCommandsDir(workspaceRoot), `${sanitized}.md`);
72
- await rm(path, { force: true });
73
- }
package/dist/config.js DELETED
@@ -1,210 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { dirname, resolve } from "node:path";
3
- import { buildWorkspaceInfos } from "./workspaces.js";
4
- import { parseList, readJsonFile, shortId } from "./utils.js";
5
- const DEFAULT_PORT = 8787;
6
- const DEFAULT_HOST = "127.0.0.1";
7
- const DEFAULT_TIMEOUT_MS = 30000;
8
- export function parseCliArgs(argv) {
9
- const args = { workspaces: [] };
10
- for (let index = 0; index < argv.length; index += 1) {
11
- const value = argv[index];
12
- if (!value)
13
- continue;
14
- if (value === "--help" || value === "-h") {
15
- args.help = true;
16
- continue;
17
- }
18
- if (value === "--config") {
19
- args.configPath = argv[index + 1];
20
- index += 1;
21
- continue;
22
- }
23
- if (value === "--host") {
24
- args.host = argv[index + 1];
25
- index += 1;
26
- continue;
27
- }
28
- if (value === "--port") {
29
- const port = Number(argv[index + 1]);
30
- if (!Number.isNaN(port))
31
- args.port = port;
32
- index += 1;
33
- continue;
34
- }
35
- if (value === "--token") {
36
- args.token = argv[index + 1];
37
- index += 1;
38
- continue;
39
- }
40
- if (value === "--host-token") {
41
- args.hostToken = argv[index + 1];
42
- index += 1;
43
- continue;
44
- }
45
- if (value === "--approval") {
46
- const mode = argv[index + 1];
47
- if (mode === "manual" || mode === "auto")
48
- args.approvalMode = mode;
49
- index += 1;
50
- continue;
51
- }
52
- if (value === "--approval-timeout") {
53
- const timeout = Number(argv[index + 1]);
54
- if (!Number.isNaN(timeout))
55
- args.approvalTimeoutMs = timeout;
56
- index += 1;
57
- continue;
58
- }
59
- if (value === "--opencode-base-url") {
60
- args.opencodeBaseUrl = argv[index + 1];
61
- index += 1;
62
- continue;
63
- }
64
- if (value === "--opencode-directory") {
65
- args.opencodeDirectory = argv[index + 1];
66
- index += 1;
67
- continue;
68
- }
69
- if (value === "--opencode-username") {
70
- args.opencodeUsername = argv[index + 1];
71
- index += 1;
72
- continue;
73
- }
74
- if (value === "--opencode-password") {
75
- args.opencodePassword = argv[index + 1];
76
- index += 1;
77
- continue;
78
- }
79
- if (value === "--workspace") {
80
- const path = argv[index + 1];
81
- if (path)
82
- args.workspaces.push(path);
83
- index += 1;
84
- continue;
85
- }
86
- if (value === "--cors") {
87
- args.corsOrigins = parseList(argv[index + 1]);
88
- index += 1;
89
- continue;
90
- }
91
- if (value === "--read-only") {
92
- args.readOnly = true;
93
- continue;
94
- }
95
- }
96
- return args;
97
- }
98
- export function printHelp() {
99
- const message = [
100
- "openwork-server",
101
- "",
102
- "Options:",
103
- " --config <path> Path to server.json",
104
- " --host <host> Hostname (default 127.0.0.1)",
105
- " --port <port> Port (default 8787)",
106
- " --token <token> Client bearer token",
107
- " --host-token <token> Host approval token",
108
- " --approval <mode> manual | auto",
109
- " --approval-timeout <ms> Approval timeout",
110
- " --opencode-base-url <url> OpenCode base URL to share",
111
- " --opencode-directory <path> OpenCode workspace directory to share",
112
- " --opencode-username <user> OpenCode server username",
113
- " --opencode-password <pass> OpenCode server password",
114
- " --workspace <path> Workspace root (repeatable)",
115
- " --cors <origins> Comma-separated origins or *",
116
- " --read-only Disable writes",
117
- ].join("\n");
118
- console.log(message);
119
- }
120
- async function loadFileConfig(configPath) {
121
- const parsed = await readJsonFile(configPath);
122
- return parsed ?? {};
123
- }
124
- export async function resolveServerConfig(cli) {
125
- const envConfigPath = process.env.OPENWORK_SERVER_CONFIG;
126
- const configPath = cli.configPath ?? envConfigPath ?? resolve(homedir(), ".config", "openwork", "server.json");
127
- const fileConfig = await loadFileConfig(configPath);
128
- const configDir = dirname(configPath);
129
- const envWorkspaces = parseList(process.env.OPENWORK_WORKSPACES);
130
- const workspaceConfigs = cli.workspaces.length > 0
131
- ? cli.workspaces.map((path) => ({ path }))
132
- : envWorkspaces.length > 0
133
- ? envWorkspaces.map((path) => ({ path }))
134
- : fileConfig.workspaces ?? [];
135
- const envOpencodeBaseUrl = process.env.OPENWORK_OPENCODE_BASE_URL;
136
- const envOpencodeDirectory = process.env.OPENWORK_OPENCODE_DIRECTORY;
137
- const envOpencodeUsername = process.env.OPENWORK_OPENCODE_USERNAME;
138
- const envOpencodePassword = process.env.OPENWORK_OPENCODE_PASSWORD;
139
- const opencodeBaseUrl = cli.opencodeBaseUrl ?? envOpencodeBaseUrl;
140
- const opencodeDirectory = cli.opencodeDirectory ?? envOpencodeDirectory;
141
- const opencodeUsername = cli.opencodeUsername ?? envOpencodeUsername ?? fileConfig.opencodeUsername;
142
- const opencodePassword = cli.opencodePassword ?? envOpencodePassword ?? fileConfig.opencodePassword;
143
- if (workspaceConfigs.length > 0 && (opencodeBaseUrl || opencodeDirectory || opencodeUsername || opencodePassword)) {
144
- workspaceConfigs[0] = {
145
- ...workspaceConfigs[0],
146
- baseUrl: opencodeBaseUrl ?? workspaceConfigs[0].baseUrl,
147
- directory: opencodeDirectory ?? workspaceConfigs[0].directory,
148
- opencodeUsername: opencodeUsername ?? workspaceConfigs[0].opencodeUsername,
149
- opencodePassword: opencodePassword ?? workspaceConfigs[0].opencodePassword,
150
- };
151
- }
152
- const workspaces = buildWorkspaceInfos(workspaceConfigs, configDir);
153
- const tokenFromEnv = process.env.OPENWORK_TOKEN;
154
- const hostTokenFromEnv = process.env.OPENWORK_HOST_TOKEN;
155
- const token = cli.token ?? tokenFromEnv ?? fileConfig.token ?? shortId();
156
- const hostToken = cli.hostToken ?? hostTokenFromEnv ?? fileConfig.hostToken ?? shortId();
157
- const tokenSource = cli.token
158
- ? "cli"
159
- : tokenFromEnv
160
- ? "env"
161
- : fileConfig.token
162
- ? "file"
163
- : "generated";
164
- const hostTokenSource = cli.hostToken
165
- ? "cli"
166
- : hostTokenFromEnv
167
- ? "env"
168
- : fileConfig.hostToken
169
- ? "file"
170
- : "generated";
171
- const approvalMode = cli.approvalMode ??
172
- process.env.OPENWORK_APPROVAL_MODE ??
173
- fileConfig.approval?.mode ??
174
- "manual";
175
- const approvalTimeoutMs = cli.approvalTimeoutMs ??
176
- (process.env.OPENWORK_APPROVAL_TIMEOUT_MS ? Number(process.env.OPENWORK_APPROVAL_TIMEOUT_MS) : undefined) ??
177
- fileConfig.approval?.timeoutMs ??
178
- DEFAULT_TIMEOUT_MS;
179
- const approval = {
180
- mode: approvalMode === "auto" ? "auto" : "manual",
181
- timeoutMs: Number.isNaN(approvalTimeoutMs) ? DEFAULT_TIMEOUT_MS : approvalTimeoutMs,
182
- };
183
- const envCorsOrigins = process.env.OPENWORK_CORS_ORIGINS;
184
- const parsedEnvCors = envCorsOrigins ? parseList(envCorsOrigins) : null;
185
- const corsOrigins = cli.corsOrigins ?? parsedEnvCors ?? fileConfig.corsOrigins ?? ["*"];
186
- const envReadOnly = process.env.OPENWORK_READONLY;
187
- const parsedReadOnly = envReadOnly
188
- ? ["true", "1", "yes"].includes(envReadOnly.toLowerCase())
189
- : undefined;
190
- const readOnly = cli.readOnly ?? parsedReadOnly ?? fileConfig.readOnly ?? false;
191
- const authorizedRoots = fileConfig.authorizedRoots?.length
192
- ? fileConfig.authorizedRoots.map((root) => resolve(configDir, root))
193
- : workspaces.map((workspace) => workspace.path);
194
- const host = cli.host ?? process.env.OPENWORK_HOST ?? fileConfig.host ?? DEFAULT_HOST;
195
- const port = cli.port ?? (process.env.OPENWORK_PORT ? Number(process.env.OPENWORK_PORT) : undefined) ?? fileConfig.port ?? DEFAULT_PORT;
196
- return {
197
- host,
198
- port: Number.isNaN(port) ? DEFAULT_PORT : port,
199
- token,
200
- hostToken,
201
- approval,
202
- corsOrigins,
203
- workspaces,
204
- authorizedRoots,
205
- readOnly,
206
- startedAt: Date.now(),
207
- tokenSource,
208
- hostTokenSource,
209
- };
210
- }
package/dist/errors.js DELETED
@@ -1,18 +0,0 @@
1
- export class ApiError extends Error {
2
- status;
3
- code;
4
- details;
5
- constructor(status, code, message, details) {
6
- super(message);
7
- this.status = status;
8
- this.code = code;
9
- this.details = details;
10
- }
11
- }
12
- export function formatError(err) {
13
- return {
14
- code: err.code,
15
- message: err.message,
16
- details: err.details,
17
- };
18
- }
@@ -1,15 +0,0 @@
1
- import { parse, stringify } from "yaml";
2
- export function parseFrontmatter(content) {
3
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
4
- if (!match) {
5
- return { data: {}, body: content };
6
- }
7
- const raw = match[1] ?? "";
8
- const data = parse(raw) ?? {};
9
- const body = content.slice(match[0].length);
10
- return { data, body };
11
- }
12
- export function buildFrontmatter(data) {
13
- const yaml = stringify(data).trimEnd();
14
- return `---\n${yaml}\n---\n`;
15
- }
package/dist/jsonc.js DELETED
@@ -1,43 +0,0 @@
1
- import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
2
- import { dirname } from "node:path";
3
- import { readFile, writeFile } from "node:fs/promises";
4
- import { ApiError } from "./errors.js";
5
- import { ensureDir, exists } from "./utils.js";
6
- export async function readJsoncFile(path, fallback) {
7
- if (!(await exists(path))) {
8
- return { data: fallback, raw: "" };
9
- }
10
- const raw = await readFile(path, "utf8");
11
- const errors = [];
12
- const data = parse(raw, errors, { allowTrailingComma: true });
13
- if (errors.length > 0) {
14
- const details = errors.map((error) => ({
15
- code: printParseErrorCode(error.error),
16
- offset: error.offset,
17
- length: error.length,
18
- }));
19
- throw new ApiError(422, "invalid_jsonc", "Failed to parse JSONC", details);
20
- }
21
- return { data, raw };
22
- }
23
- export async function updateJsoncTopLevel(path, updates) {
24
- const hasFile = await exists(path);
25
- if (!hasFile) {
26
- await ensureDir(dirname(path));
27
- const content = JSON.stringify(updates, null, 2) + "\n";
28
- await writeFile(path, content, "utf8");
29
- return;
30
- }
31
- let content = await readFile(path, "utf8");
32
- const formattingOptions = { insertSpaces: true, tabSize: 2, eol: "\n" };
33
- for (const [key, value] of Object.entries(updates)) {
34
- const edits = modify(content, [key], value, { formattingOptions });
35
- content = applyEdits(content, edits);
36
- }
37
- await writeFile(path, content.endsWith("\n") ? content : content + "\n", "utf8");
38
- }
39
- export async function writeJsoncFile(path, value) {
40
- await ensureDir(dirname(path));
41
- const content = JSON.stringify(value, null, 2) + "\n";
42
- await writeFile(path, content, "utf8");
43
- }