@thxgg/steward 0.1.10 → 0.1.11
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/e2995e80-736c-47cd-8041-a131bab2f136.json +1 -0
- package/.output/server/chunks/nitro/nitro.mjs +604 -542
- package/.output/server/package.json +1 -1
- package/README.md +9 -0
- package/bin/prd +117 -0
- package/dist/host/src/mcp.js +2 -0
- package/dist/server/utils/db.js +86 -1
- package/docs/MCP.md +14 -0
- package/package.json +1 -1
- package/.output/public/_nuxt/builds/meta/7fda7510-94bc-443a-a338-a8d2af142ed9.json +0 -1
package/README.md
CHANGED
|
@@ -41,6 +41,15 @@ Add to your MCP client config:
|
|
|
41
41
|
}
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Steward MCP requires a Node runtime with built-in sqlite support (`node:sqlite`) for `repos`, `prds`, and `state` APIs.
|
|
45
|
+
If you see `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite`, run with sqlite enabled:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
NODE_OPTIONS=--experimental-sqlite npx -y @thxgg/steward mcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Note: `execute` runs in a VM sandbox by design, so globals like `process` are intentionally not exposed.
|
|
52
|
+
|
|
44
53
|
### CLI
|
|
45
54
|
|
|
46
55
|
```bash
|
package/bin/prd
CHANGED
|
@@ -1,11 +1,128 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
2
3
|
import { existsSync } from 'node:fs'
|
|
3
4
|
import { dirname, resolve } from 'node:path'
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
5
6
|
|
|
7
|
+
const SQLITE_ENABLE_FLAG = '--experimental-sqlite'
|
|
8
|
+
const SQLITE_DISABLE_FLAG = '--no-experimental-sqlite'
|
|
9
|
+
const SQLITE_REEXEC_ENV = 'STEWARD_SQLITE_REEXEC'
|
|
10
|
+
|
|
6
11
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
7
12
|
const entryPath = resolve(packageRoot, 'dist', 'host', 'src', 'index.js')
|
|
8
13
|
|
|
14
|
+
function requiresSqliteRuntime(command) {
|
|
15
|
+
return command === 'mcp' || command === 'ui'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isSqliteRuntimeError(error) {
|
|
19
|
+
if (!(error instanceof Error)) {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const code = error.code
|
|
24
|
+
return typeof code === 'string'
|
|
25
|
+
&& code === 'ERR_UNKNOWN_BUILTIN_MODULE'
|
|
26
|
+
&& error.message.includes('node:sqlite')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sanitizeNodeOptions(rawNodeOptions = '') {
|
|
30
|
+
const options = rawNodeOptions
|
|
31
|
+
.split(/\s+/)
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.filter((option) => option !== SQLITE_ENABLE_FLAG && option !== SQLITE_DISABLE_FLAG)
|
|
34
|
+
|
|
35
|
+
return options.join(' ')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildReexecEnv() {
|
|
39
|
+
const env = { ...process.env }
|
|
40
|
+
const sanitizedNodeOptions = sanitizeNodeOptions(env.NODE_OPTIONS)
|
|
41
|
+
|
|
42
|
+
if (sanitizedNodeOptions.length > 0) {
|
|
43
|
+
env.NODE_OPTIONS = sanitizedNodeOptions
|
|
44
|
+
} else {
|
|
45
|
+
delete env.NODE_OPTIONS
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
env[SQLITE_REEXEC_ENV] = '1'
|
|
49
|
+
return env
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function canEnableSqliteWithFlag() {
|
|
53
|
+
const probe = spawnSync(
|
|
54
|
+
process.execPath,
|
|
55
|
+
[SQLITE_ENABLE_FLAG, '-e', "import('node:sqlite').then(() => process.exit(0)).catch(() => process.exit(1))"],
|
|
56
|
+
{
|
|
57
|
+
stdio: 'ignore',
|
|
58
|
+
env: buildReexecEnv()
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return probe.status === 0
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function reexecWithSqliteFlag() {
|
|
66
|
+
const result = spawnSync(
|
|
67
|
+
process.execPath,
|
|
68
|
+
[SQLITE_ENABLE_FLAG, fileURLToPath(import.meta.url), ...process.argv.slice(2)],
|
|
69
|
+
{
|
|
70
|
+
stdio: 'inherit',
|
|
71
|
+
env: buildReexecEnv()
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if (result.error) {
|
|
76
|
+
throw result.error
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
process.exit(result.status ?? 1)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function ensureSqliteRuntime(command) {
|
|
83
|
+
if (!requiresSqliteRuntime(command)) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await import('node:sqlite')
|
|
89
|
+
return
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (!isSqliteRuntimeError(error)) {
|
|
92
|
+
throw error
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const alreadyReexecuted = process.env[SQLITE_REEXEC_ENV] === '1'
|
|
96
|
+
if (!alreadyReexecuted && canEnableSqliteWithFlag()) {
|
|
97
|
+
reexecWithSqliteFlag()
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
102
|
+
const nodeOptions = process.env.NODE_OPTIONS || ''
|
|
103
|
+
const hasDisableFlag = process.execArgv.includes(SQLITE_DISABLE_FLAG)
|
|
104
|
+
|| nodeOptions.includes(SQLITE_DISABLE_FLAG)
|
|
105
|
+
|
|
106
|
+
console.error('Steward requires SQLite runtime support for repos/prds/state APIs.')
|
|
107
|
+
console.error(`Runtime: ${process.version} (${process.execPath})`)
|
|
108
|
+
if (nodeOptions) {
|
|
109
|
+
console.error(`NODE_OPTIONS: ${nodeOptions}`)
|
|
110
|
+
}
|
|
111
|
+
console.error(`Cause: ${message}`)
|
|
112
|
+
|
|
113
|
+
if (hasDisableFlag) {
|
|
114
|
+
console.error(`Hint: remove ${SQLITE_DISABLE_FLAG} from NODE_OPTIONS or runtime args and retry.`)
|
|
115
|
+
} else {
|
|
116
|
+
console.error(`Hint: use a Node runtime with sqlite support or launch with ${SQLITE_ENABLE_FLAG}.`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
process.exit(1)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const command = process.argv[2] || ''
|
|
124
|
+
await ensureSqliteRuntime(command)
|
|
125
|
+
|
|
9
126
|
if (!existsSync(entryPath)) {
|
|
10
127
|
console.error('Steward host runtime is not built.')
|
|
11
128
|
console.error('Run `npm run build:host` in this package before invoking `prd`.')
|
package/dist/host/src/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import { assertSqliteRuntimeSupport } from '../../server/utils/db.js';
|
|
4
5
|
import { execute } from './executor.js';
|
|
5
6
|
import { getExecuteToolDescription } from './help.js';
|
|
6
7
|
function serializeEnvelope(envelope) {
|
|
@@ -49,6 +50,7 @@ function buildUnexpectedErrorEnvelope(error) {
|
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
export async function runMcpServer() {
|
|
53
|
+
await assertSqliteRuntimeSupport();
|
|
52
54
|
const server = new McpServer({
|
|
53
55
|
name: 'steward',
|
|
54
56
|
version: '0.1.0'
|
package/dist/server/utils/db.js
CHANGED
|
@@ -3,7 +3,19 @@ import { dirname, join } from 'node:path';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
const DEFAULT_DATA_HOME = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share');
|
|
5
5
|
const DEFAULT_DB_PATH = join(DEFAULT_DATA_HOME, 'prd', 'state.db');
|
|
6
|
+
const SQLITE_ENABLE_FLAG = '--experimental-sqlite';
|
|
7
|
+
const SQLITE_DISABLE_FLAG = '--no-experimental-sqlite';
|
|
6
8
|
let adapterPromise = null;
|
|
9
|
+
export class SqliteRuntimeError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
details;
|
|
12
|
+
constructor(message, details) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'SqliteRuntimeError';
|
|
15
|
+
this.code = 'SQLITE_RUNTIME_UNSUPPORTED';
|
|
16
|
+
this.details = details;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
7
19
|
function coerceChanges(result) {
|
|
8
20
|
if (!result || typeof result !== 'object') {
|
|
9
21
|
return 0;
|
|
@@ -22,11 +34,84 @@ function resolveDbPath() {
|
|
|
22
34
|
}
|
|
23
35
|
return DEFAULT_DB_PATH;
|
|
24
36
|
}
|
|
37
|
+
function formatNodeRuntimeHint() {
|
|
38
|
+
const nodeOptions = process.env.NODE_OPTIONS || '';
|
|
39
|
+
const hasDisableFlag = process.execArgv.includes(SQLITE_DISABLE_FLAG)
|
|
40
|
+
|| nodeOptions.includes(SQLITE_DISABLE_FLAG);
|
|
41
|
+
if (hasDisableFlag) {
|
|
42
|
+
return `Remove ${SQLITE_DISABLE_FLAG} from NODE_OPTIONS or Node arguments.`;
|
|
43
|
+
}
|
|
44
|
+
return `Use Node.js with built-in sqlite support, or launch with ${SQLITE_ENABLE_FLAG}.`;
|
|
45
|
+
}
|
|
46
|
+
function mapSqliteImportError(error, dbPath) {
|
|
47
|
+
if (!(error instanceof Error)) {
|
|
48
|
+
return new SqliteRuntimeError('SQLite runtime support is unavailable in this process.', {
|
|
49
|
+
runtime: 'node',
|
|
50
|
+
dbPath,
|
|
51
|
+
nodeVersion: process.version,
|
|
52
|
+
execPath: process.execPath,
|
|
53
|
+
execArgv: [...process.execArgv],
|
|
54
|
+
nodeOptions: process.env.NODE_OPTIONS || '',
|
|
55
|
+
originalMessage: String(error)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const code = error.code;
|
|
59
|
+
const isMissingBuiltin = typeof code === 'string'
|
|
60
|
+
&& code === 'ERR_UNKNOWN_BUILTIN_MODULE'
|
|
61
|
+
&& error.message.includes('node:sqlite');
|
|
62
|
+
const isMissingModule = typeof code === 'string'
|
|
63
|
+
&& code === 'ERR_MODULE_NOT_FOUND'
|
|
64
|
+
&& error.message.includes('node:sqlite');
|
|
65
|
+
if (!isMissingBuiltin && !isMissingModule) {
|
|
66
|
+
return error;
|
|
67
|
+
}
|
|
68
|
+
return new SqliteRuntimeError(`SQLite runtime support is unavailable for Steward (${process.version}). ${formatNodeRuntimeHint()}`, {
|
|
69
|
+
runtime: 'node',
|
|
70
|
+
dbPath,
|
|
71
|
+
nodeVersion: process.version,
|
|
72
|
+
execPath: process.execPath,
|
|
73
|
+
execArgv: [...process.execArgv],
|
|
74
|
+
nodeOptions: process.env.NODE_OPTIONS || '',
|
|
75
|
+
originalCode: typeof code === 'string' ? code : undefined,
|
|
76
|
+
originalMessage: error.message
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function loadNodeSqlite(dbPath) {
|
|
80
|
+
try {
|
|
81
|
+
return await import('node:sqlite');
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw mapSqliteImportError(error, dbPath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
25
87
|
export function getDbPath() {
|
|
26
88
|
return resolveDbPath();
|
|
27
89
|
}
|
|
90
|
+
export async function assertSqliteRuntimeSupport() {
|
|
91
|
+
const dbPath = resolveDbPath();
|
|
92
|
+
const isBunRuntime = typeof globalThis.Bun !== 'undefined';
|
|
93
|
+
if (isBunRuntime) {
|
|
94
|
+
try {
|
|
95
|
+
const bunModuleName = 'bun:sqlite';
|
|
96
|
+
await import(bunModuleName);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const code = error instanceof Error
|
|
101
|
+
? error.code
|
|
102
|
+
: undefined;
|
|
103
|
+
throw new SqliteRuntimeError('SQLite runtime support is unavailable in Bun runtime.', {
|
|
104
|
+
runtime: 'bun',
|
|
105
|
+
dbPath,
|
|
106
|
+
originalCode: typeof code === 'string' ? code : undefined,
|
|
107
|
+
originalMessage: error instanceof Error ? error.message : String(error)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await loadNodeSqlite(dbPath);
|
|
112
|
+
}
|
|
28
113
|
async function createNodeAdapter(dbPath) {
|
|
29
|
-
const sqliteModule = await
|
|
114
|
+
const sqliteModule = await loadNodeSqlite(dbPath);
|
|
30
115
|
const db = new sqliteModule.DatabaseSync(dbPath);
|
|
31
116
|
return {
|
|
32
117
|
exec(sql) {
|
package/docs/MCP.md
CHANGED
|
@@ -46,6 +46,20 @@ Example MCP client config:
|
|
|
46
46
|
}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
## Runtime Requirements
|
|
50
|
+
|
|
51
|
+
- `repos`, `prds`, and `state` APIs require sqlite runtime support.
|
|
52
|
+
- Steward uses Node's built-in `node:sqlite` module.
|
|
53
|
+
- `prd mcp` auto-retries with `--experimental-sqlite` when the runtime supports it.
|
|
54
|
+
|
|
55
|
+
If you still see `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite`, launch explicitly with:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
NODE_OPTIONS=--experimental-sqlite npx -y @thxgg/steward mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If that still fails, the MCP host is using an incompatible Node runtime.
|
|
62
|
+
|
|
49
63
|
## Execute Contract
|
|
50
64
|
|
|
51
65
|
`execute` expects one input field:
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"7fda7510-94bc-443a-a338-a8d2af142ed9","timestamp":1771867324818,"prerendered":[]}
|