xedoc-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/bin/xedoc.mjs +233 -0
- package/build/client/assets/api.accounts-CHoT6sYP.js +0 -0
- package/build/client/assets/api.accounts._accountId-BixDYyx8.js +0 -0
- package/build/client/assets/api.accounts._accountId.authenticate-CLzgv3py.js +0 -0
- package/build/client/assets/api.accounts._accountId.authenticate.callback-Bqhz8sDl.js +0 -0
- package/build/client/assets/api.accounts._accountId.models-VIVpVZrj.js +0 -0
- package/build/client/assets/api.accounts._accountId.rate-limits-C9iUvNFt.js +0 -0
- package/build/client/assets/api.accounts._accountId.runtime-settings-6XNKTx--.js +0 -0
- package/build/client/assets/api.accounts.export-romutOT6.js +0 -0
- package/build/client/assets/api.accounts.import-BiWq2jPz.js +0 -0
- package/build/client/assets/api.auth.exchange-CkPWDiGV.js +0 -0
- package/build/client/assets/api.auth.session-DOeXMy_2.js +0 -0
- package/build/client/assets/api.auth.status-FGYJfLVM.js +0 -0
- package/build/client/assets/api.chats-C6-gVaXw.js +0 -0
- package/build/client/assets/api.chats._chatId-DaXHa-ln.js +0 -0
- package/build/client/assets/api.chats._chatId.context-DlBgjpCD.js +0 -0
- package/build/client/assets/api.chats._chatId.files-DVZKcoNV.js +0 -0
- package/build/client/assets/api.chats._chatId.git._operation-8Xtyz4hF.js +0 -0
- package/build/client/assets/api.chats._chatId.interrupt-B71lrxkc.js +0 -0
- package/build/client/assets/api.chats._chatId.messages-iVzJcgvw.js +0 -0
- package/build/client/assets/api.chats._chatId.server-requests._requestId.respond-BLhOUK8A.js +0 -0
- package/build/client/assets/api.users-BLvMDkJE.js +0 -0
- package/build/client/assets/api.workspaces.directories-B0tllRDq.js +0 -0
- package/build/client/assets/app-layout-C1QtSnIO.js +1 -0
- package/build/client/assets/app-shell-Bcxy4tS4.js +1 -0
- package/build/client/assets/app-shell-DdKuH37F.css +1 -0
- package/build/client/assets/chat-DrelwLpW.js +1 -0
- package/build/client/assets/connect-0oYrfAJT.js +1 -0
- package/build/client/assets/document-title-Dn4sU16M.js +1 -0
- package/build/client/assets/entry.client-CM3vH2bv.js +1 -0
- package/build/client/assets/favicon.ico-BGeA4faG.js +0 -0
- package/build/client/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/build/client/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/build/client/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/build/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/build/client/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/build/client/assets/health-OjIoaxn7.js +0 -0
- package/build/client/assets/home-DSWMwIrE.js +1 -0
- package/build/client/assets/jsx-runtime-B9QlDcuB.js +1 -0
- package/build/client/assets/label-FUU4pCWu.js +1 -0
- package/build/client/assets/manifest-9479dd15.js +1 -0
- package/build/client/assets/react-dom-KYDDPtOx.js +1 -0
- package/build/client/assets/root-Bh_1OXF0.css +2 -0
- package/build/client/assets/root-DKEnPIjJ.js +1 -0
- package/build/client/assets/session-provider-FKGj36EG.js +1 -0
- package/build/client/favicon.svg +1 -0
- package/build/client/icons.svg +24 -0
- package/build/server/index.js +1 -0
- package/package.json +88 -0
- package/prisma/schema.prisma +171 -0
- package/server/index.mjs +261 -0
- package/server/sqlite-setup.mjs +155 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# xedoc
|
|
2
|
+
|
|
3
|
+
xedoc is a single React Router Framework Mode app, built with Vite, for managing Codex accounts, chats, chat execution, workspace browsing, and live assistant output.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- Stores users, Codex accounts, chat metadata, runs, and live message projections in a local SQLite database through Prisma.
|
|
8
|
+
- Starts Codex account authentication through the local `codex app-server` JSON-RPC flow.
|
|
9
|
+
- Isolates each Codex account with a separate `CODEX_HOME`.
|
|
10
|
+
- Stores a working directory on each chat so different chats can target different local projects.
|
|
11
|
+
- Executes chat prompts against the selected Codex account and reads settled transcripts back from Codex runtime/session data.
|
|
12
|
+
- Serves the ChatGPT-style web UI and `/api/*` resource routes from one same-origin app.
|
|
13
|
+
- Streams live chat updates through authenticated Socket.IO rooms on `/socket.io`.
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
The easiest local install is the npm CLI:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx xedoc
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
By default the CLI creates a standalone SQLite database at
|
|
24
|
+
`~/.xedoc/xedoc.db`, prepares the schema, and serves the app at
|
|
25
|
+
`http://127.0.0.1:6354`. Open the app in a browser and set the server password
|
|
26
|
+
on first visit. Use a different database file with:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx xedoc --database-path ~/.local/share/xedoc.db
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Common CLI options:
|
|
33
|
+
|
|
34
|
+
- `--port <port>` changes the web server port.
|
|
35
|
+
- `--workspace-root <path>` changes the directory tree visible to the app.
|
|
36
|
+
- `--accounts-home <path>` changes where Codex account state is stored.
|
|
37
|
+
- `--database-path <path>` changes where the SQLite database is stored.
|
|
38
|
+
- `--skip-setup` skips SQLite schema setup.
|
|
39
|
+
|
|
40
|
+
For repository development:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm install
|
|
44
|
+
cp .env.example .env
|
|
45
|
+
pnpm prisma:generate
|
|
46
|
+
pnpm db:setup
|
|
47
|
+
pnpm dev
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
On first visit, the web app asks you to set the server password and stores a
|
|
51
|
+
hashed password plus token signing secret in the SQLite database. Later browser
|
|
52
|
+
sessions exchange that password for a bearer token, then store the token,
|
|
53
|
+
browser user id, and active account id in local storage.
|
|
54
|
+
|
|
55
|
+
The default `DATABASE_URL` uses a SQLite file under `.xedoc/` in the
|
|
56
|
+
project. Set `DATABASE_URL=file:/absolute/path/to/xedoc.db` to use a
|
|
57
|
+
specific database file.
|
|
58
|
+
|
|
59
|
+
## Scripts
|
|
60
|
+
|
|
61
|
+
- `pnpm dev` starts the React Router dev server.
|
|
62
|
+
- `pnpm build` builds the app.
|
|
63
|
+
- `pnpm start` serves the production React Router build through `server/index.mjs` and attaches Socket.IO.
|
|
64
|
+
- `pnpm db:setup` creates or updates the local SQLite schema.
|
|
65
|
+
- `pnpm prisma:generate` regenerates Prisma Client.
|
|
66
|
+
- `pnpm run publish` publishes the package to npm with public access. npm runs `prepack`, which builds the production bundle first.
|
|
67
|
+
- `pnpm run publish:dry-run` checks the npm package contents without publishing.
|
|
68
|
+
|
|
69
|
+
## npm Releases
|
|
70
|
+
|
|
71
|
+
Package releases are published by GitHub Actions when a tag matching `v*` is
|
|
72
|
+
pushed. The workflow uses npm trusted publishing, so configure the package on
|
|
73
|
+
npmjs.com with this GitHub repository and the workflow file
|
|
74
|
+
`.github/workflows/npm-publish.yml` before pushing the first release tag.
|
|
75
|
+
|
|
76
|
+
## Codex Account Isolation
|
|
77
|
+
|
|
78
|
+
Each Codex account runs as its own local `codex app-server` process. The server sets `CODEX_HOME` per account so auth files, config, sessions, cache, and other Codex state stay under:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
~/.xedoc/accounts/<accountId>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Set `CODEX_ACCOUNTS_HOME` to change the base directory. This isolates Codex account state only; Codex still runs as the same host user and can access whatever that user can access.
|
|
85
|
+
|
|
86
|
+
Set `CODEX_WORKSPACE_ROOT` to the directory the web app can browse for chat
|
|
87
|
+
working directories. Local development defaults to the current user's home
|
|
88
|
+
directory, for example `/home/ubuntu`.
|
|
89
|
+
|
|
90
|
+
## API Entry Points
|
|
91
|
+
|
|
92
|
+
- `GET /health`
|
|
93
|
+
- `GET /api/auth/status`
|
|
94
|
+
- `POST /api/auth/exchange`
|
|
95
|
+
- `GET /api/auth/session`
|
|
96
|
+
- `POST /api/users`
|
|
97
|
+
- `GET /api/accounts`
|
|
98
|
+
- `POST /api/accounts`
|
|
99
|
+
- `GET /api/accounts/:accountId`
|
|
100
|
+
- `PATCH /api/accounts/:accountId`
|
|
101
|
+
- `DELETE /api/accounts/:accountId`
|
|
102
|
+
- `POST /api/accounts/:accountId/authenticate`
|
|
103
|
+
- `POST /api/accounts/:accountId/authenticate/callback`
|
|
104
|
+
- `GET /api/chats`
|
|
105
|
+
- `POST /api/chats`
|
|
106
|
+
- `GET /api/chats/:chatId`
|
|
107
|
+
- `PATCH /api/chats/:chatId`
|
|
108
|
+
- `DELETE /api/chats/:chatId`
|
|
109
|
+
- `GET /api/chats/:chatId/messages`
|
|
110
|
+
- `POST /api/chats/:chatId/messages`
|
|
111
|
+
- `GET /api/workspaces/directories`
|
package/bin/xedoc.mjs
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process"
|
|
3
|
+
import { mkdir, readFile } from "node:fs/promises"
|
|
4
|
+
import { homedir } from "node:os"
|
|
5
|
+
import { dirname, join, resolve } from "node:path"
|
|
6
|
+
import { fileURLToPath } from "node:url"
|
|
7
|
+
import { createRequire } from "node:module"
|
|
8
|
+
|
|
9
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..")
|
|
10
|
+
const require = createRequire(import.meta.url)
|
|
11
|
+
const packageJson = JSON.parse(
|
|
12
|
+
await readFile(join(packageRoot, "package.json"), "utf8"),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const options = parseArgs(process.argv.slice(2))
|
|
16
|
+
if (options.help) {
|
|
17
|
+
printHelp()
|
|
18
|
+
process.exit(0)
|
|
19
|
+
}
|
|
20
|
+
if (options.version) {
|
|
21
|
+
console.log(packageJson.version)
|
|
22
|
+
process.exit(0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const appHome = resolveHomePath(
|
|
26
|
+
options.home ?? process.env.XEDOC_HOME ?? "~/.xedoc",
|
|
27
|
+
)
|
|
28
|
+
await mkdir(appHome, { recursive: true, mode: 0o700 })
|
|
29
|
+
|
|
30
|
+
const port = options.port ?? process.env.PORT ?? "6354"
|
|
31
|
+
const host = options.host ?? process.env.HOST ?? "127.0.0.1"
|
|
32
|
+
const accountsHome = resolveHomePath(
|
|
33
|
+
options.accountsHome ??
|
|
34
|
+
process.env.CODEX_ACCOUNTS_HOME ??
|
|
35
|
+
join(appHome, "accounts"),
|
|
36
|
+
)
|
|
37
|
+
const workspaceRoot = resolveHomePath(
|
|
38
|
+
options.workspaceRoot ?? process.env.CODEX_WORKSPACE_ROOT ?? homedir(),
|
|
39
|
+
)
|
|
40
|
+
const databasePath = resolveHomePath(
|
|
41
|
+
options.databasePath ??
|
|
42
|
+
process.env.XEDOC_DATABASE ??
|
|
43
|
+
join(appHome, "xedoc.db"),
|
|
44
|
+
)
|
|
45
|
+
await mkdir(dirname(databasePath), { recursive: true, mode: 0o700 })
|
|
46
|
+
const databaseUrl =
|
|
47
|
+
options.databaseUrl ??
|
|
48
|
+
process.env.DATABASE_URL ??
|
|
49
|
+
`file:${databasePath}`
|
|
50
|
+
|
|
51
|
+
const codexBin = require.resolve("@openai/codex/bin/codex.js")
|
|
52
|
+
const env = {
|
|
53
|
+
...process.env,
|
|
54
|
+
CODEX_ACCOUNTS_HOME: accountsHome,
|
|
55
|
+
CODEX_ARGS: options.codexArgs ?? process.env.CODEX_ARGS ?? `${codexBin} app-server`,
|
|
56
|
+
CODEX_COMMAND: options.codexCommand ?? process.env.CODEX_COMMAND ?? process.execPath,
|
|
57
|
+
CODEX_WORKSPACE_ROOT: workspaceRoot,
|
|
58
|
+
DATABASE_URL: databaseUrl,
|
|
59
|
+
HOST: host,
|
|
60
|
+
NODE_ENV: "production",
|
|
61
|
+
PORT: port,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await mkdir(accountsHome, { recursive: true, mode: 0o700 })
|
|
65
|
+
if (!options.skipPrismaGenerate) {
|
|
66
|
+
await runPrisma(["generate"], env)
|
|
67
|
+
}
|
|
68
|
+
if (!options.skipSetup) {
|
|
69
|
+
await setupSqliteDatabase(env)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`
|
|
73
|
+
console.log(`xedoc: ${url}`)
|
|
74
|
+
console.log("Set the server password in your browser on first visit.")
|
|
75
|
+
console.log(`Workspace root: ${workspaceRoot}`)
|
|
76
|
+
console.log("Press Ctrl+C to stop.")
|
|
77
|
+
|
|
78
|
+
await runServer(env)
|
|
79
|
+
|
|
80
|
+
function parseArgs(argv) {
|
|
81
|
+
const parsed = {}
|
|
82
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
83
|
+
const arg = argv[index]
|
|
84
|
+
if (arg === "--help" || arg === "-h") {
|
|
85
|
+
parsed.help = true
|
|
86
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
87
|
+
parsed.version = true
|
|
88
|
+
} else if (arg === "--skip-setup") {
|
|
89
|
+
parsed.skipSetup = true
|
|
90
|
+
} else if (arg === "--skip-prisma-generate") {
|
|
91
|
+
parsed.skipPrismaGenerate = true
|
|
92
|
+
} else if (arg.startsWith("--")) {
|
|
93
|
+
const [name, inlineValue] = arg.split("=", 2)
|
|
94
|
+
const value = inlineValue ?? argv[++index]
|
|
95
|
+
if (!value || value.startsWith("--")) {
|
|
96
|
+
fail(`${name} requires a value.`)
|
|
97
|
+
}
|
|
98
|
+
assignOption(parsed, name, value)
|
|
99
|
+
} else {
|
|
100
|
+
fail(`Unknown argument: ${arg}`)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return parsed
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function assignOption(parsed, name, value) {
|
|
107
|
+
switch (name) {
|
|
108
|
+
case "--accounts-home":
|
|
109
|
+
parsed.accountsHome = value
|
|
110
|
+
return
|
|
111
|
+
case "--codex-args":
|
|
112
|
+
parsed.codexArgs = value
|
|
113
|
+
return
|
|
114
|
+
case "--codex-command":
|
|
115
|
+
parsed.codexCommand = value
|
|
116
|
+
return
|
|
117
|
+
case "--database-url":
|
|
118
|
+
parsed.databaseUrl = value
|
|
119
|
+
return
|
|
120
|
+
case "--database-path":
|
|
121
|
+
parsed.databasePath = value
|
|
122
|
+
return
|
|
123
|
+
case "--home":
|
|
124
|
+
parsed.home = value
|
|
125
|
+
return
|
|
126
|
+
case "--host":
|
|
127
|
+
parsed.host = value
|
|
128
|
+
return
|
|
129
|
+
case "--port":
|
|
130
|
+
parsed.port = value
|
|
131
|
+
return
|
|
132
|
+
case "--workspace-root":
|
|
133
|
+
parsed.workspaceRoot = value
|
|
134
|
+
return
|
|
135
|
+
default:
|
|
136
|
+
fail(`Unknown option: ${name}`)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function runPrisma(args, env) {
|
|
141
|
+
await run(process.execPath, [
|
|
142
|
+
require.resolve("prisma/build/index.js"),
|
|
143
|
+
...args,
|
|
144
|
+
"--schema",
|
|
145
|
+
join(packageRoot, "prisma/schema.prisma"),
|
|
146
|
+
], {
|
|
147
|
+
cwd: packageRoot,
|
|
148
|
+
env,
|
|
149
|
+
stdio: "inherit",
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function runServer(env) {
|
|
154
|
+
await run(process.execPath, [join(packageRoot, "server/index.mjs")], {
|
|
155
|
+
cwd: packageRoot,
|
|
156
|
+
env,
|
|
157
|
+
stdio: "inherit",
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function setupSqliteDatabase(env) {
|
|
162
|
+
const previousDatabaseUrl = process.env.DATABASE_URL
|
|
163
|
+
process.env.DATABASE_URL = env.DATABASE_URL
|
|
164
|
+
try {
|
|
165
|
+
const { setupSqliteDatabase } = await import(
|
|
166
|
+
join(packageRoot, "server/sqlite-setup.mjs")
|
|
167
|
+
)
|
|
168
|
+
await setupSqliteDatabase()
|
|
169
|
+
} finally {
|
|
170
|
+
if (previousDatabaseUrl === undefined) {
|
|
171
|
+
delete process.env.DATABASE_URL
|
|
172
|
+
} else {
|
|
173
|
+
process.env.DATABASE_URL = previousDatabaseUrl
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function run(command, args, options) {
|
|
179
|
+
return new Promise((resolveRun, rejectRun) => {
|
|
180
|
+
const child = spawn(command, args, options)
|
|
181
|
+
const forwardSigint = () => child.kill("SIGINT")
|
|
182
|
+
const forwardSigterm = () => child.kill("SIGTERM")
|
|
183
|
+
process.once("SIGINT", forwardSigint)
|
|
184
|
+
process.once("SIGTERM", forwardSigterm)
|
|
185
|
+
child.on("exit", (code, signal) => {
|
|
186
|
+
process.removeListener("SIGINT", forwardSigint)
|
|
187
|
+
process.removeListener("SIGTERM", forwardSigterm)
|
|
188
|
+
if (code === 0 || signal) {
|
|
189
|
+
resolveRun()
|
|
190
|
+
} else {
|
|
191
|
+
rejectRun(new Error(`${command} exited with code ${code}`))
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
child.on("error", rejectRun)
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function resolveHomePath(path) {
|
|
199
|
+
if (path === "~") {
|
|
200
|
+
return homedir()
|
|
201
|
+
}
|
|
202
|
+
if (path.startsWith("~/")) {
|
|
203
|
+
return join(homedir(), path.slice(2))
|
|
204
|
+
}
|
|
205
|
+
return resolve(path)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function fail(message) {
|
|
209
|
+
console.error(message)
|
|
210
|
+
process.exit(1)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function printHelp() {
|
|
214
|
+
console.log(`xedoc ${packageJson.version}
|
|
215
|
+
|
|
216
|
+
Usage:
|
|
217
|
+
npx xedoc [options]
|
|
218
|
+
|
|
219
|
+
Options:
|
|
220
|
+
--port <port> Web server port. Defaults to 6354.
|
|
221
|
+
--host <host> Web server host. Defaults to 127.0.0.1.
|
|
222
|
+
--workspace-root <path> Directory tree visible in the app. Defaults to your home directory.
|
|
223
|
+
--accounts-home <path> Codex account state directory. Defaults to ~/.xedoc/accounts.
|
|
224
|
+
--database-path <path> SQLite database path. Defaults to ~/.xedoc/xedoc.db.
|
|
225
|
+
--database-url <url> SQLite DATABASE_URL. Defaults to file:<database-path>.
|
|
226
|
+
--skip-setup Do not create the SQLite database schema.
|
|
227
|
+
--codex-command <command> Codex command used for new accounts.
|
|
228
|
+
--codex-args <args> Codex command arguments used for new accounts.
|
|
229
|
+
--home <path> App data directory. Defaults to ~/.xedoc.
|
|
230
|
+
--help Show this help.
|
|
231
|
+
--version Print the package version.
|
|
232
|
+
`)
|
|
233
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{j as r,t as s}from"./jsx-runtime-B9QlDcuB.js";import{t}from"./app-shell-Bcxy4tS4.js";var a=s(),o=r(function(){return(0,a.jsx)(t,{})});export{o as default};
|