@playcraft/cli 0.0.59 → 0.1.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/README.md +14 -2
- package/dist/agent/local-backend.js +1 -1
- package/dist/utils/agent-api-client.js +50 -7
- package/dist/utils/tool-operation-journal.js +82 -16
- package/dist/workspace-runtime/codex/app-server.js +135 -169
- package/dist/workspace-runtime/codex/jsonrpc-stdio.js +19 -2
- package/dist/workspace-runtime/codex/launcher-client.js +155 -0
- package/dist/workspace-runtime/codex/loopback.js +118 -37
- package/dist/workspace-runtime/codex/native-adapter.js +3 -0
- package/dist/workspace-runtime/persistence/journal.js +213 -200
- package/dist/workspace-runtime/persistence/sqlite-client.js +61 -0
- package/dist/workspace-runtime/persistence/sqlite-worker.js +1025 -0
- package/dist/workspace-runtime/serve.js +35 -1
- package/dist/workspace-runtime/server/desktop-host.js +314 -0
- package/dist/workspace-runtime/server/dispatch.js +108 -48
- package/dist/workspace-runtime/server/http.js +260 -12
- package/dist/workspace-runtime/server/terminal-host.js +243 -0
- package/dist/workspace-runtime/workspaces/git-credential-broker.js +276 -0
- package/dist/workspace-runtime/workspaces/git-credential-helper.js +66 -0
- package/dist/workspace-runtime/workspaces/git-direct-store.js +154 -0
- package/dist/workspace-runtime/workspaces/git.js +16 -3
- package/dist/workspace-runtime/workspaces/prepare.js +29 -172
- package/dist/workspace-runtime/workspaces/registry.js +12 -2
- package/package.json +7 -4
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { randomUUID
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { matchWorkspaceGitGatewayCloneUrl, workspaceGitAccessSchema, workspaceGitTransportSchema, workspacePreparationSchema, workspaceRegistrationSchema, } from '@playcraft/common/cloud-workspace/runtime';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { lstat, mkdir, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { isLocalWorkspaceGitAuthMode, isLocalWorkspaceGitEnvironment, matchWorkspaceGitGatewayCloneUrl, workspacePreparationSchema, workspaceRegistrationSchema, } from '@playcraft/common/cloud-workspace/runtime';
|
|
7
6
|
import { WorkspaceContextError } from './context-error.js';
|
|
8
7
|
/** The caller holds the Workspace lock. Only an absent, never-published checkout may be cloned. */
|
|
9
8
|
export async function prepareWorkspaceCheckout(state, binding, input, leaseFence) {
|
|
@@ -11,6 +10,10 @@ export async function prepareWorkspaceCheckout(state, binding, input, leaseFence
|
|
|
11
10
|
if (!preparation.success)
|
|
12
11
|
throw new WorkspaceContextError('invalid_workspace_preparation');
|
|
13
12
|
const source = parseCloneSource(binding.repositoryUrl);
|
|
13
|
+
const direct = isLocalWorkspaceGitAuthMode(preparation.data.authMode);
|
|
14
|
+
if (direct && (!isLocalWorkspaceGitEnvironment(state.config.environment) || !preparation.data.directAccess
|
|
15
|
+
|| preparation.data.directAccess.repositoryUrl !== binding.repositoryUrl))
|
|
16
|
+
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
14
17
|
const parent = await realpath(state.config.workspaceRoot);
|
|
15
18
|
const target = join(parent, binding.workspaceId);
|
|
16
19
|
const receiptDir = join(state.config.stateDir, 'workspace-preparations');
|
|
@@ -40,19 +43,17 @@ export async function prepareWorkspaceCheckout(state, binding, input, leaseFence
|
|
|
40
43
|
await rm(staging, { recursive: true, force: true });
|
|
41
44
|
await mkdir(staging, { mode: 0o700 });
|
|
42
45
|
const access = preparation.data.gitAccess;
|
|
46
|
+
const credential = preparation.data.directAccess ?? access;
|
|
43
47
|
const now = Date.now();
|
|
44
48
|
if (access &&
|
|
45
|
-
(Date.parse(access.issuedAt) > now || Date.parse(access.expiresAt) <= now + 10_000))
|
|
49
|
+
(Date.parse(access.issuedAt) > now || Date.parse(access.expiresAt) <= now + 10_000)) {
|
|
50
|
+
await rm(staging, { recursive: true, force: true });
|
|
46
51
|
throw new WorkspaceContextError('workspace_git_access_expired');
|
|
47
|
-
let gateway;
|
|
48
|
-
try {
|
|
49
|
-
gateway = access
|
|
50
|
-
? await mountGateway(state, binding.repositoryUrl, access, preparation.data.transport)
|
|
51
|
-
: undefined;
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
const transport = preparation.data.transport;
|
|
54
|
+
if (transport && !matchWorkspaceGitGatewayCloneUrl(transport.cloneUrl, state.config.gitGatewayOrigins, binding.repositoryUrl).ok) {
|
|
54
55
|
await rm(staging, { recursive: true, force: true });
|
|
55
|
-
throw
|
|
56
|
+
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
56
57
|
}
|
|
57
58
|
const deadline = Math.min(now + 90_000, access ? Date.parse(access.expiresAt) : Infinity);
|
|
58
59
|
const env = {
|
|
@@ -63,23 +64,16 @@ export async function prepareWorkspaceCheckout(state, binding, input, leaseFence
|
|
|
63
64
|
GIT_TERMINAL_PROMPT: '0',
|
|
64
65
|
GIT_ALLOW_PROTOCOL: 'http:https',
|
|
65
66
|
};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
env.GIT_CONFIG_COUNT = '1';
|
|
70
|
-
env.GIT_CONFIG_KEY_0 = `
|
|
71
|
-
env.GIT_CONFIG_VALUE_0 =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
cloneConfig.push(`credential.helper=!${shellQuote(process.execPath)} -e ${shellQuote('eval(Buffer.from(process.env.PLAYCRAFT_GIT_HELPER,"base64").toString())')}`, 'credential.useHttpPath=true');
|
|
77
|
-
}
|
|
78
|
-
else if (access) {
|
|
79
|
-
// Authentication exists only in the clone process environment; never in arguments or .git/config.
|
|
80
|
-
env.GIT_CONFIG_COUNT = '1';
|
|
81
|
-
env.GIT_CONFIG_KEY_0 = `http.${source.href}.extraHeader`;
|
|
82
|
-
env.GIT_CONFIG_VALUE_0 = `Authorization: Basic ${Buffer.from(`${access.username}:${access.password}`).toString('base64')}`;
|
|
67
|
+
// The authenticated registration already carries this operation's scoped credential.
|
|
68
|
+
// No publisher ledger is mounted in the sandbox; secrets exist only in this child environment.
|
|
69
|
+
if (credential) {
|
|
70
|
+
env.GIT_CONFIG_COUNT = transport ? '2' : '1';
|
|
71
|
+
env.GIT_CONFIG_KEY_0 = `http.${transport?.cloneUrl ?? source.href}.extraHeader`;
|
|
72
|
+
env.GIT_CONFIG_VALUE_0 = `Authorization: Basic ${Buffer.from(`${credential.username}:${credential.password}`).toString('base64')}`;
|
|
73
|
+
if (transport) {
|
|
74
|
+
env.GIT_CONFIG_KEY_1 = `url.${transport.cloneUrl}.insteadOf`;
|
|
75
|
+
env.GIT_CONFIG_VALUE_1 = binding.repositoryUrl;
|
|
76
|
+
}
|
|
83
77
|
}
|
|
84
78
|
try {
|
|
85
79
|
await runGit(['check-ref-format', '--branch', binding.branchName], env, deadline);
|
|
@@ -92,7 +86,7 @@ export async function prepareWorkspaceCheckout(state, binding, input, leaseFence
|
|
|
92
86
|
'--',
|
|
93
87
|
binding.repositoryUrl,
|
|
94
88
|
staging,
|
|
95
|
-
], env, deadline
|
|
89
|
+
], env, deadline);
|
|
96
90
|
// Checkout never receives the temporary credential and never invokes repository hooks or global filters.
|
|
97
91
|
clearCloneSecrets(env);
|
|
98
92
|
await runGit(['-C', staging, 'checkout', '--force', binding.branchName], env, deadline);
|
|
@@ -106,6 +100,9 @@ export async function prepareWorkspaceCheckout(state, binding, input, leaseFence
|
|
|
106
100
|
throw error;
|
|
107
101
|
throw new WorkspaceContextError('workspace_clone_failed');
|
|
108
102
|
}
|
|
103
|
+
finally {
|
|
104
|
+
clearCloneSecrets(env);
|
|
105
|
+
}
|
|
109
106
|
}
|
|
110
107
|
const stat = await lstat(staging).catch(() => undefined);
|
|
111
108
|
if (!stat?.isDirectory() || stat.isSymbolicLink())
|
|
@@ -124,149 +121,10 @@ function clearCloneSecrets(env) {
|
|
|
124
121
|
'GIT_CONFIG_VALUE_0',
|
|
125
122
|
'GIT_CONFIG_KEY_1',
|
|
126
123
|
'GIT_CONFIG_VALUE_1',
|
|
127
|
-
'PLAYCRAFT_GIT_HELPER',
|
|
128
|
-
'PLAYCRAFT_GIT_CREDENTIAL_DIRECTORY',
|
|
129
|
-
'PLAYCRAFT_GIT_GATEWAY_CLONE_URL',
|
|
130
|
-
'PLAYCRAFT_GIT_CANONICAL_URL',
|
|
131
124
|
]) {
|
|
132
125
|
delete env[key];
|
|
133
126
|
}
|
|
134
127
|
}
|
|
135
|
-
function shellQuote(value) {
|
|
136
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
137
|
-
}
|
|
138
|
-
/** Read-only open of the publisher's control directory. Credentials are not copied into the checkout. */
|
|
139
|
-
async function mountGateway(state, repositoryUrl, access, transport) {
|
|
140
|
-
if (!transport)
|
|
141
|
-
return undefined;
|
|
142
|
-
const directory = state.config.gitCredentialDirectory;
|
|
143
|
-
const matched = matchWorkspaceGitGatewayCloneUrl(transport.cloneUrl, state.config.gitGatewayOrigins, repositoryUrl);
|
|
144
|
-
if (!matched.ok || !directory)
|
|
145
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
146
|
-
const parsed = workspaceGitTransportSchema.parse(transport);
|
|
147
|
-
if (parsed.cloneUrl !== transport.cloneUrl) {
|
|
148
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
149
|
-
}
|
|
150
|
-
const entry = await readMountedEntry(directory, transport.cloneUrl, matched.requestId, repositoryUrl);
|
|
151
|
-
if (entry.username !== access.username ||
|
|
152
|
-
entry.issuedAt !== access.issuedAt ||
|
|
153
|
-
entry.expiresAt !== access.expiresAt ||
|
|
154
|
-
!sameSecret(entry.password, access.password)) {
|
|
155
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
156
|
-
}
|
|
157
|
-
return { directory, cloneUrl: transport.cloneUrl };
|
|
158
|
-
}
|
|
159
|
-
async function readMountedEntry(directory, cloneUrl, requestId, repositoryUrl) {
|
|
160
|
-
if (!isAbsolute(directory) || (await realpath(directory)) !== resolve(directory)) {
|
|
161
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
162
|
-
}
|
|
163
|
-
const directoryStat = await lstat(directory);
|
|
164
|
-
if (!directoryStat.isDirectory() ||
|
|
165
|
-
directoryStat.isSymbolicLink() ||
|
|
166
|
-
(directoryStat.mode & 0o077) !== 0 ||
|
|
167
|
-
(typeof process.getuid === 'function' && directoryStat.uid !== process.getuid())) {
|
|
168
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
169
|
-
}
|
|
170
|
-
const directoryHandle = await open(directory, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_DIRECTORY);
|
|
171
|
-
await directoryHandle.close();
|
|
172
|
-
const file = await open(join(directory, 'workspace-git-credentials.json'), constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
173
|
-
try {
|
|
174
|
-
const stat = await file.stat();
|
|
175
|
-
if (!stat.isFile() || (stat.mode & 0o077) !== 0 || stat.size > 1024 * 1024) {
|
|
176
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
177
|
-
}
|
|
178
|
-
const document = JSON.parse(await file.readFile('utf8'));
|
|
179
|
-
if (blocked(document).has(requestId)) {
|
|
180
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
181
|
-
}
|
|
182
|
-
const entries = (document.entries ?? []).filter((entry) => entry.transport &&
|
|
183
|
-
typeof entry.transport === 'object' &&
|
|
184
|
-
entry.transport.cloneUrl === cloneUrl);
|
|
185
|
-
if (entries.length !== 1)
|
|
186
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
187
|
-
const entry = entries[0];
|
|
188
|
-
if (entry.repositoryUrl !== repositoryUrl ||
|
|
189
|
-
requestIdOf(entry.command) !== requestId ||
|
|
190
|
-
requestIdOf(entry.receipt) !== requestId) {
|
|
191
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
192
|
-
}
|
|
193
|
-
return workspaceGitAccessSchema.parse(entry.gitAccess);
|
|
194
|
-
}
|
|
195
|
-
finally {
|
|
196
|
-
await file.close();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
function requestIdOf(value) {
|
|
200
|
-
if (!value || typeof value !== 'object')
|
|
201
|
-
return undefined;
|
|
202
|
-
const id = value.requestId;
|
|
203
|
-
return typeof id === 'string' ? id : undefined;
|
|
204
|
-
}
|
|
205
|
-
function blocked(document) {
|
|
206
|
-
const ids = new Set();
|
|
207
|
-
for (const list of [document.retired, document.revocations]) {
|
|
208
|
-
if (list === undefined)
|
|
209
|
-
continue;
|
|
210
|
-
if (!Array.isArray(list))
|
|
211
|
-
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
212
|
-
for (const item of list) {
|
|
213
|
-
const id = requestIdOf(item);
|
|
214
|
-
if (id)
|
|
215
|
-
ids.add(id);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return ids;
|
|
219
|
-
}
|
|
220
|
-
function sameSecret(left, right) {
|
|
221
|
-
const a = Buffer.from(left);
|
|
222
|
-
const b = Buffer.from(right);
|
|
223
|
-
return a.length === b.length && timingSafeEqual(a, b);
|
|
224
|
-
}
|
|
225
|
-
const GIT_GATEWAY_CREDENTIAL_HELPER = `
|
|
226
|
-
const fs = require('node:fs');
|
|
227
|
-
const path = require('node:path');
|
|
228
|
-
const action = process.argv[1];
|
|
229
|
-
if (action !== 'get') process.exit(0);
|
|
230
|
-
const chunks = [];
|
|
231
|
-
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
232
|
-
process.stdin.on('end', () => {
|
|
233
|
-
try {
|
|
234
|
-
const fields = {};
|
|
235
|
-
for (const line of Buffer.concat(chunks).toString('utf8').split('\\n')) {
|
|
236
|
-
if (!line) continue;
|
|
237
|
-
const index = line.indexOf('=');
|
|
238
|
-
if (index > 0) fields[line.slice(0, index)] = line.slice(index + 1);
|
|
239
|
-
}
|
|
240
|
-
const cloneUrl = new URL(process.env.PLAYCRAFT_GIT_GATEWAY_CLONE_URL);
|
|
241
|
-
const canonical = new URL(process.env.PLAYCRAFT_GIT_CANONICAL_URL);
|
|
242
|
-
if (!fields.path || !fields.protocol || !fields.host) process.exit(1);
|
|
243
|
-
const requested = new URL(fields.protocol + '://' + fields.host + '/' + String(fields.path).replace(/^\\/+/, ''));
|
|
244
|
-
if (requested.origin !== cloneUrl.origin || requested.pathname !== cloneUrl.pathname || requested.origin === canonical.origin) process.exit(1);
|
|
245
|
-
const requestId = cloneUrl.pathname.split('/').pop();
|
|
246
|
-
const directory = process.env.PLAYCRAFT_GIT_CREDENTIAL_DIRECTORY;
|
|
247
|
-
const flags = fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;
|
|
248
|
-
const dir = fs.openSync(directory, flags | (fs.constants.O_DIRECTORY || 0));
|
|
249
|
-
try {
|
|
250
|
-
if (!fs.fstatSync(dir).isDirectory()) process.exit(1);
|
|
251
|
-
} finally { fs.closeSync(dir); }
|
|
252
|
-
const file = fs.openSync(path.join(directory, 'workspace-git-credentials.json'), flags);
|
|
253
|
-
try {
|
|
254
|
-
const stat = fs.fstatSync(file);
|
|
255
|
-
if (!stat.isFile() || (stat.mode & 0o077) !== 0 || stat.size > 1024 * 1024) process.exit(1);
|
|
256
|
-
const document = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
257
|
-
const blocked = [...(document.retired || []), ...(document.revocations || [])];
|
|
258
|
-
if (blocked.some((item) => item && item.requestId === requestId)) process.exit(1);
|
|
259
|
-
const entries = (document.entries || []).filter((entry) => entry && entry.transport && entry.transport.cloneUrl === process.env.PLAYCRAFT_GIT_GATEWAY_CLONE_URL && entry.repositoryUrl === process.env.PLAYCRAFT_GIT_CANONICAL_URL);
|
|
260
|
-
if (entries.length !== 1) process.exit(1);
|
|
261
|
-
const access = entries[0].gitAccess || {};
|
|
262
|
-
if (Date.parse(access.issuedAt) > Date.now() || Date.parse(access.expiresAt) <= Date.now() + 10000) process.exit(1);
|
|
263
|
-
if (!access.username || !access.password || /[\\r\\n\\0]/.test(access.username) || /[\\r\\n\\0]/.test(access.password)) process.exit(1);
|
|
264
|
-
process.stdout.write('username=' + access.username + '\\npassword=' + access.password + '\\n');
|
|
265
|
-
process.exit(0);
|
|
266
|
-
} finally { fs.closeSync(file); }
|
|
267
|
-
} catch { process.exit(1); }
|
|
268
|
-
});
|
|
269
|
-
`;
|
|
270
128
|
function parseCloneSource(value) {
|
|
271
129
|
try {
|
|
272
130
|
const source = new URL(value);
|
|
@@ -316,7 +174,7 @@ async function writeReceipt(path, value) {
|
|
|
316
174
|
await writeFile(temp, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
317
175
|
await rename(temp, path);
|
|
318
176
|
}
|
|
319
|
-
function runGit(args, env, deadline
|
|
177
|
+
function runGit(args, env, deadline) {
|
|
320
178
|
return new Promise((resolve, reject) => {
|
|
321
179
|
let timedOut = false;
|
|
322
180
|
const child = spawn('git', [
|
|
@@ -324,7 +182,6 @@ function runGit(args, env, deadline, config = []) {
|
|
|
324
182
|
'core.hooksPath=/dev/null',
|
|
325
183
|
'-c',
|
|
326
184
|
'credential.helper=',
|
|
327
|
-
...config.flatMap((item) => ['-c', item]),
|
|
328
185
|
'-c',
|
|
329
186
|
'http.followRedirects=false',
|
|
330
187
|
'-c',
|
|
@@ -2,7 +2,7 @@ import { execFile } from 'node:child_process';
|
|
|
2
2
|
import { lstat, realpath } from 'node:fs/promises';
|
|
3
3
|
import { join, relative } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
-
import { workspaceRegistrationSchema, } from '@playcraft/common/cloud-workspace/runtime';
|
|
5
|
+
import { isLocalWorkspaceGitAuthMode, workspaceRegistrationSchema, } from '@playcraft/common/cloud-workspace/runtime';
|
|
6
6
|
import { prepareWorkspaceCheckout } from './prepare.js';
|
|
7
7
|
import { WorkspaceContextError } from './context-error.js';
|
|
8
8
|
export { WorkspaceContextError } from './context-error.js';
|
|
@@ -87,7 +87,7 @@ export async function registerWorkspace(state, input, leaseFence, preparation) {
|
|
|
87
87
|
})).stdout.trim();
|
|
88
88
|
try {
|
|
89
89
|
if ((await realpath(await git('rev-parse', '--show-toplevel'))) !== root ||
|
|
90
|
-
(await git('
|
|
90
|
+
(await git('config', '--get', 'remote.origin.url')) !== binding.repositoryUrl ||
|
|
91
91
|
(await git('symbolic-ref', '--short', 'HEAD')) !== binding.branchName) {
|
|
92
92
|
throw new WorkspaceContextError('workspace_checkout_mismatch');
|
|
93
93
|
}
|
|
@@ -101,6 +101,16 @@ export async function registerWorkspace(state, input, leaseFence, preparation) {
|
|
|
101
101
|
throw new WorkspaceContextError('runtime_destroyed');
|
|
102
102
|
if (leaseFence < state.acceptedLeaseFence)
|
|
103
103
|
throw new WorkspaceContextError('stale_lease');
|
|
104
|
+
if (preparation?.transport?.kind === 'git-gateway-v2' || isLocalWorkspaceGitAuthMode(preparation?.authMode)) {
|
|
105
|
+
if (!state.gitCredentials)
|
|
106
|
+
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
107
|
+
try {
|
|
108
|
+
await state.gitCredentials.configure(binding, preparation);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
throw new WorkspaceContextError('workspace_git_gateway_unavailable');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
104
114
|
await state.journal.putWorkspace(binding);
|
|
105
115
|
return binding;
|
|
106
116
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcraft/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dev": "tsc -w",
|
|
25
25
|
"build": "tsc",
|
|
26
26
|
"start": "node dist/index.js",
|
|
27
|
-
"test": "vitest run",
|
|
27
|
+
"test": "pnpm build && vitest run",
|
|
28
28
|
"test:agent": "node scripts/run-agent-tests.mjs --list",
|
|
29
29
|
"test:agent:designer": "node scripts/run-agent-tests.mjs game-designer",
|
|
30
30
|
"test:agent:developer": "node scripts/run-agent-tests.mjs developer",
|
|
@@ -48,8 +48,9 @@
|
|
|
48
48
|
"@gltf-transform/core": "^4.3.0",
|
|
49
49
|
"@gltf-transform/extensions": "^4.3.0",
|
|
50
50
|
"@gltf-transform/functions": "^4.3.0",
|
|
51
|
-
"@playcraft/build": "^0.
|
|
52
|
-
"@playcraft/common": "^0.
|
|
51
|
+
"@playcraft/build": "^0.1.1",
|
|
52
|
+
"@playcraft/common": "^0.1.1",
|
|
53
|
+
"better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
|
|
53
54
|
"chokidar": "^4.0.3",
|
|
54
55
|
"commander": "^13.1.0",
|
|
55
56
|
"cors": "^2.8.6",
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"json5": "^2.2.3",
|
|
66
67
|
"latest-version": "^7.0.0",
|
|
67
68
|
"music-metadata": "^11.12.3",
|
|
69
|
+
"node-pty": "1.0.0",
|
|
68
70
|
"ora": "^8.2.0",
|
|
69
71
|
"picocolors": "^1.1.1",
|
|
70
72
|
"sharp": "^0.34.5",
|
|
@@ -82,6 +84,7 @@
|
|
|
82
84
|
"@types/sharp": "^0.32.0",
|
|
83
85
|
"@types/update-notifier": "^6.0.8",
|
|
84
86
|
"@types/ws": "^8.18.1",
|
|
87
|
+
"esbuild": "^0.28.1",
|
|
85
88
|
"typescript": "^5.9.3",
|
|
86
89
|
"vitest": "^3.2.4"
|
|
87
90
|
}
|