@xuda.io/ai_module 1.1.5658 → 1.1.5660

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/index_ms.mjs CHANGED
@@ -69,28 +69,28 @@ export const git_repo_list = async function (...args) {
69
69
  return await broker.send_to_queue("git_repo_list", ...args);
70
70
  };
71
71
 
72
- export const xudex_key_set = async function (...args) {
73
- return await broker.send_to_queue("xudex_key_set", ...args);
72
+ export const xucode_key_set = async function (...args) {
73
+ return await broker.send_to_queue("xucode_key_set", ...args);
74
74
  };
75
75
 
76
- export const xudex_key_list = async function (...args) {
77
- return await broker.send_to_queue("xudex_key_list", ...args);
76
+ export const xucode_key_list = async function (...args) {
77
+ return await broker.send_to_queue("xucode_key_list", ...args);
78
78
  };
79
79
 
80
- export const xudex_key_delete = async function (...args) {
81
- return await broker.send_to_queue("xudex_key_delete", ...args);
80
+ export const xucode_key_delete = async function (...args) {
81
+ return await broker.send_to_queue("xucode_key_delete", ...args);
82
82
  };
83
83
 
84
- export const xudex_engine_list = async function (...args) {
85
- return await broker.send_to_queue("xudex_engine_list", ...args);
84
+ export const xucode_engine_list = async function (...args) {
85
+ return await broker.send_to_queue("xucode_engine_list", ...args);
86
86
  };
87
87
 
88
88
  export const git_repo_disconnect = async function (...args) {
89
89
  return await broker.send_to_queue("git_repo_disconnect", ...args);
90
90
  };
91
91
 
92
- export const xudex_run = async function (...args) {
93
- return await broker.send_to_queue("xudex_run", ...args);
92
+ export const xucode_run = async function (...args) {
93
+ return await broker.send_to_queue("xucode_run", ...args);
94
94
  };
95
95
 
96
96
  export const git_repo_status = async function (...args) {
package/index_msa.mjs CHANGED
@@ -69,28 +69,28 @@ export const git_repo_list = function (...args) {
69
69
  broker.send_to_queue_async("git_repo_list", ...args);
70
70
  };
71
71
 
72
- export const xudex_key_set = function (...args) {
73
- broker.send_to_queue_async("xudex_key_set", ...args);
72
+ export const xucode_key_set = function (...args) {
73
+ broker.send_to_queue_async("xucode_key_set", ...args);
74
74
  };
75
75
 
76
- export const xudex_key_list = function (...args) {
77
- broker.send_to_queue_async("xudex_key_list", ...args);
76
+ export const xucode_key_list = function (...args) {
77
+ broker.send_to_queue_async("xucode_key_list", ...args);
78
78
  };
79
79
 
80
- export const xudex_key_delete = function (...args) {
81
- broker.send_to_queue_async("xudex_key_delete", ...args);
80
+ export const xucode_key_delete = function (...args) {
81
+ broker.send_to_queue_async("xucode_key_delete", ...args);
82
82
  };
83
83
 
84
- export const xudex_engine_list = function (...args) {
85
- broker.send_to_queue_async("xudex_engine_list", ...args);
84
+ export const xucode_engine_list = function (...args) {
85
+ broker.send_to_queue_async("xucode_engine_list", ...args);
86
86
  };
87
87
 
88
88
  export const git_repo_disconnect = function (...args) {
89
89
  broker.send_to_queue_async("git_repo_disconnect", ...args);
90
90
  };
91
91
 
92
- export const xudex_run = function (...args) {
93
- broker.send_to_queue_async("xudex_run", ...args);
92
+ export const xucode_run = function (...args) {
93
+ broker.send_to_queue_async("xucode_run", ...args);
94
94
  };
95
95
 
96
96
  export const git_repo_status = function (...args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/ai_module",
3
- "version": "1.1.5658",
3
+ "version": "1.1.5660",
4
4
  "description": "Xuda AI Module",
5
5
  "main": "index.mjs",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
- // Xudex engines: the seam between "an agent did some work" and "which CLI did it".
1
+ // Xucode engines: the seam between "an agent did some work" and "which CLI did it".
2
2
  //
3
- // docs/plans/xudex.md 5.1. An engine is four things and no more:
3
+ // docs/plans/xucode.md 5.1. An engine is four things and no more:
4
4
  //
5
5
  // 1. AUTH what environment it needs to be allowed to run
6
6
  // 2. A LAUNCH the argv, including how to resume an existing thread
@@ -41,7 +41,7 @@ if (!global._conf) {
41
41
  global._conf = (await import(path.join(process.env.XUDA_HOME, 'common', 'load_conf.mjs'))).loadConf();
42
42
  }
43
43
 
44
- const engines_conf = () => global._conf.xudex?.engines || {};
44
+ const engines_conf = () => global._conf.xucode?.engines || {};
45
45
 
46
46
  const num = (...values) => {
47
47
  for (const v of values) {
@@ -132,6 +132,48 @@ const codex_adapter = {
132
132
  return api_key ? { OPENAI_API_KEY: api_key } : {};
133
133
  },
134
134
 
135
+ // Point codex at the region-server proxy instead of the provider, so the machine holds a
136
+ // run token rather than a key. See xucode_proxy.mjs for why that trade is worth making.
137
+ //
138
+ // OPENAI_BASE_URL DOES NOT WORK for this, which is worth stating because it is the obvious
139
+ // thing to reach for: codex 0.128.0 ignores it outright. Verified against the real binary by
140
+ // pointing it at a listener that recorded nothing while the turn completed against the live
141
+ // API. The supported redirect is a custom model_provider, whose `env_key` names the variable
142
+ // codex puts in the Authorization header, which is exactly the slot our token wants.
143
+ proxy_launch({ base_url, token, home } = {}) {
144
+ const config = [
145
+ '# Written by Xucode for this run. The token below is a run-scoped credential for the',
146
+ '# Xuda proxy, not a provider key: it is signed, it expires with the run, and it is only',
147
+ '# accepted by this one endpoint.',
148
+ 'model_provider = "xucode"',
149
+ '',
150
+ '# `codex exec` sandboxes to READ-ONLY by default, so without this the agent reads the',
151
+ '# project, decides on a change, cannot write it, and reports success over an unchanged',
152
+ '# repository. Verified the hard way: a run that made three model calls and produced no',
153
+ '# files. workspace-write is the right level rather than a full bypass, because it is also',
154
+ '# correct when the substrate is the region server itself, where "the machine is the',
155
+ '# sandbox" is not true.',
156
+ 'sandbox_mode = "workspace-write"',
157
+ '# Nothing is watching to approve anything: a prompt here is a run that hangs until it',
158
+ '# times out.',
159
+ 'approval_policy = "never"',
160
+ '',
161
+ '[model_providers.xucode]',
162
+ 'name = "Xucode"',
163
+ `base_url = "${base_url}/v1"`,
164
+ 'env_key = "XUCODE_TOKEN"',
165
+ 'wire_api = "responses"',
166
+ '',
167
+ ].join('\n');
168
+ return {
169
+ // CODEX_HOME moves the whole config directory, which also guarantees the machine cannot
170
+ // fall back to a stored auth.json: there is nothing in a fresh home to fall back to.
171
+ env: { CODEX_HOME: home, XUCODE_TOKEN: token },
172
+ files: [{ path: `${home}/config.toml`, content: config }],
173
+ scratch: home,
174
+ };
175
+ },
176
+
135
177
  normalize(event) {
136
178
  if (!event || typeof event !== 'object') return [];
137
179
  switch (event.type) {
@@ -190,6 +232,15 @@ const claude_code_adapter = {
190
232
  return api_key ? { ANTHROPIC_API_KEY: api_key } : {};
191
233
  },
192
234
 
235
+ // The same seam, for the day BYOK keys move behind the proxy too. Claude Code reads
236
+ // ANTHROPIC_BASE_URL and takes its credential from ANTHROPIC_AUTH_TOKEN, so no config file
237
+ // is needed. UNVERIFIED against the real CLI, like the rest of this adapter: nothing routes
238
+ // through here yet, because a customer's own key on a customer's own machine is not the
239
+ // problem the proxy was built to solve.
240
+ proxy_launch({ base_url, token } = {}) {
241
+ return { env: { ANTHROPIC_BASE_URL: base_url, ANTHROPIC_AUTH_TOKEN: token }, files: [] };
242
+ },
243
+
193
244
  normalize(event) {
194
245
  if (!event || typeof event !== 'object') return [];
195
246
  switch (event.type) {
@@ -255,21 +306,37 @@ export const get_engine = function (key) {
255
306
  // A BYOK engine with no key is refused HERE, before anything is spawned. The alternative is
256
307
  // a run that starts, burns a workspace slot, and dies on a provider 401 that reads to the
257
308
  // customer like our failure.
258
- export const prepare_launch = function ({ engine: engine_key, model, resume_session_id, api_key, sandbox, bypass_sandbox } = {}) {
309
+ export const prepare_launch = function ({ engine: engine_key, model, resume_session_id, api_key, sandbox, bypass_sandbox, proxy = null } = {}) {
259
310
  const engine = get_engine(engine_key);
260
311
  if (!engine) return { error: 'That engine is not available.' };
261
- if (engine.byok && !api_key) {
312
+ // A proxied launch satisfies the credential requirement without a key being present HERE: the
313
+ // customer's key stays on the region server and the proxy attaches it. The caller is responsible
314
+ // for only minting a token once it knows the account actually has a key, which it checks without
315
+ // reading one.
316
+ if (engine.byok && !api_key && !proxy) {
262
317
  return {
263
318
  error: `${engine.label} runs on your own API key. Add an ${engine.provider === 'anthropic' ? 'Anthropic' : engine.provider} API key in settings, then try again.`,
264
319
  needs_key: engine.provider,
265
320
  };
266
321
  }
322
+
323
+ // The proxy REPLACES the credential rather than sitting alongside it. Leaving a real key in
324
+ // the environment as well would defeat the point twice over: the engine would prefer the key
325
+ // and skip the proxy, and the key would be on the machine anyway.
326
+ const proxied = proxy && typeof engine.proxy_launch === 'function' ? engine.proxy_launch(proxy) : null;
327
+
267
328
  return {
268
329
  engine: engine.key,
269
330
  label: engine.label,
270
331
  command: engine.command,
271
332
  args: engine.build_args({ model, resume_session_id, sandbox, bypass_sandbox }),
272
- env: engine.env({ api_key }),
333
+ env: proxied ? { ...engine.env({}), ...proxied.env } : engine.env({ api_key }),
334
+ // Files the machine needs before the engine starts. The runner writes them without knowing
335
+ // what they are, which keeps "codex needs a TOML" from becoming everyone's problem.
336
+ files: proxied?.files || [],
337
+ // Where those files went, so the runner can remove them without knowing what they were.
338
+ scratch_dir: proxied?.scratch || null,
339
+ proxied: !!proxied,
273
340
  resuming: !!resume_session_id,
274
341
  };
275
342
  };
@@ -310,7 +377,7 @@ export const create_run_state = function () {
310
377
  case 'error':
311
378
  // Verbatim, per plan 5.2. With BYOK we cannot see the customer's balance or rate
312
379
  // limits, so a 429 from their account must read as their provider saying no, not
313
- // as Xudex breaking.
380
+ // as Xucode breaking.
314
381
  state.error = event.message;
315
382
  break;
316
383
  case 'done':
@@ -1,6 +1,6 @@
1
- // Xudex git mirror: how a repository reaches a machine we do not fully trust.
1
+ // Xucode git mirror: how a repository reaches a machine we do not fully trust.
2
2
  //
3
- // docs/plans/xudex.md 9.2. UI-226's verified property is that a repository token never leaves the
3
+ // docs/plans/xucode.md 9.2. UI-226's verified property is that a repository token never leaves the
4
4
  // region server: it is validated by an authenticated ls-remote before anything is stored, kept on a
5
5
  // git_repo doc in the account's project database rather than in xuda_master (which replicates
6
6
  // fleet-wide), and handed to git only through GIT_ASKPASS so it never lands in an argv, in
@@ -28,6 +28,7 @@
28
28
  // default.
29
29
 
30
30
  import fs from 'fs';
31
+ import os from 'os';
31
32
  import path from 'node:path';
32
33
 
33
34
  const DEFAULTS = {
@@ -66,7 +67,7 @@ export const create_mirror = function ({ git_exec, du_mb, mirror_root, clone_tim
66
67
  await fs.promises.rm(dir, { recursive: true, force: true }).catch(() => {});
67
68
  const cloned = await git_exec({ args: ['clone', '--bare', '--no-tags', repo.remote_url, dir], token, timeout_ms: CLONE_TIMEOUT_MS });
68
69
  if (cloned.exit_code !== 0) {
69
- console.error(`[xudex] mirror clone failed for ${repo._id}: exit ${cloned.exit_code}`);
70
+ console.error(`[xucode] mirror clone failed for ${repo._id}: exit ${cloned.exit_code}`);
70
71
  return { error: 'Xuda could not clone that repository.' };
71
72
  }
72
73
  // A bare clone carries no fetch refspec, so without this the mirror would never learn about a
@@ -83,7 +84,7 @@ export const create_mirror = function ({ git_exec, du_mb, mirror_root, clone_tim
83
84
  const fetched = await git_exec({ cwd: dir, args: ['fetch', '--prune', 'origin'], token, timeout_ms: CLONE_TIMEOUT_MS });
84
85
  // A stale mirror still serves a clone, so a failed refresh is a warning rather than an error:
85
86
  // the customer gets slightly old code instead of no code, and the next run tries again.
86
- if (fetched.exit_code !== 0) console.warn(`[xudex] mirror fetch failed for ${repo._id}: exit ${fetched.exit_code}`);
87
+ if (fetched.exit_code !== 0) console.warn(`[xucode] mirror fetch failed for ${repo._id}: exit ${fetched.exit_code}`);
87
88
  return { dir, created: false, stale: fetched.exit_code !== 0 };
88
89
  },
89
90
 
@@ -94,7 +95,7 @@ export const create_mirror = function ({ git_exec, du_mb, mirror_root, clone_tim
94
95
  await fs.promises.rm(dest, { recursive: true, force: true }).catch(() => {});
95
96
  const cloned = await git_exec({ args: ['clone', '--no-tags', from, dest], timeout_ms: CLONE_TIMEOUT_MS });
96
97
  if (cloned.exit_code !== 0) {
97
- console.error(`[xudex] workspace clone from mirror failed: exit ${cloned.exit_code}`);
98
+ console.error(`[xucode] workspace clone from mirror failed: exit ${cloned.exit_code}`);
98
99
  return { error: 'Xuda could not prepare a working copy for this project.' };
99
100
  }
100
101
 
@@ -128,7 +129,7 @@ export const create_mirror = function ({ git_exec, du_mb, mirror_root, clone_tim
128
129
  if (!has_git) return await this.clone_workspace({ mirror_dir: from, dest, branch, default_branch });
129
130
 
130
131
  const fetched = await git_exec({ cwd: dest, args: ['fetch', '--prune', 'origin'], timeout_ms: CLONE_TIMEOUT_MS });
131
- if (fetched.exit_code !== 0) console.warn(`[xudex] workspace fetch from mirror failed: exit ${fetched.exit_code}`);
132
+ if (fetched.exit_code !== 0) console.warn(`[xucode] workspace fetch from mirror failed: exit ${fetched.exit_code}`);
132
133
 
133
134
  if (branch) {
134
135
  // Reuse the branch when it is already here rather than resetting onto the remote: a
@@ -153,13 +154,119 @@ export const create_mirror = function ({ git_exec, du_mb, mirror_root, clone_tim
153
154
  return { dir: dest, branch: branch || null, base_sha: head.exit_code === 0 ? head.stdout : null, reused: true };
154
155
  },
155
156
 
157
+ // ── Getting the repository INTO a workspace that cannot reach the mirror ────────────────
158
+ // The mirror is a bare repo on the region server's disk. A Xucode machine is a different
159
+ // computer, keyless by design and reachable only through the Proxmox guest agent, so it cannot
160
+ // clone from a path it cannot see and must not be handed a key so that it could.
161
+ //
162
+ // A git BUNDLE is the answer git already has for this: one file carrying real objects and refs,
163
+ // clonable and fetchable like any remote, and needing no network path between the two machines.
164
+ // It crosses as base64 text, which is the only thing the agent channel carries.
165
+ //
166
+ // The cost is honest and worth stating: the first hydration of a large repository moves the
167
+ // whole history as base64 in 60 KB chunks, which is slow. It is correct everywhere today with
168
+ // no new infrastructure and no credentials on the machine. Making the mirror reachable over the
169
+ // WireGuard mesh would turn this into a plain clone and is the optimisation, not the design.
170
+ async hydrate_workspace({ workspace, uid, repo, branch, default_branch }) {
171
+ const dir = mirror_dir(uid, repo._id);
172
+ const bundle = path.join(os.tmpdir(), `xucode-${repo._id}-${Date.now()}.bundle`);
173
+ try {
174
+ // --all rather than one branch: the workspace needs the default branch to cut from as well
175
+ // as any branch this chat already has.
176
+ const made = await git_exec({ cwd: dir, args: ['bundle', 'create', bundle, '--all'], timeout_ms: CLONE_TIMEOUT_MS });
177
+ if (made.exit_code !== 0) return { error: 'Xuda could not package that repository for the machine.' };
178
+ const b64 = await fs.promises.readFile(bundle, 'base64');
179
+
180
+ // Does the workspace already hold a checkout? A warm machine keeps its working copy between
181
+ // runs, and that warmth is most of what the paid tiers sell, so this fetches rather than
182
+ // re-cloning and never touches uncommitted work.
183
+ const existing = await workspace.exec(['sh', '-c', 'test -d .git && echo yes || echo no'], { timeout_ms: 60000 });
184
+ const has_repo = (existing.stdout || '').includes('yes');
185
+
186
+ await workspace.write_file('.xucode_repo.b64', b64);
187
+ // GNU base64 decodes with -d, BSD (macOS) with -D and has no --decode. A workspace can be
188
+ // either: the VM is Linux, a developer running the local substrate is not. Same shape of
189
+ // trap as setsid in the preview, and it only ever shows up on the machine you are not on.
190
+ // Read from STDIN, not a positional filename. GNU base64 accepts a path, BSD (macOS) does
191
+ // not and answers "invalid argument" while still creating the empty output file, so the
192
+ // failure looks like a corrupt bundle rather than a wrong flag. Redirection works on both,
193
+ // and so does -d on both, which the -D fallback obscured.
194
+ const unpack = await workspace.exec(['sh', '-c', 'base64 -d < .xucode_repo.b64 > .xucode_repo.bundle && rm -f .xucode_repo.b64'], { timeout_ms: CLONE_TIMEOUT_MS });
195
+ if (unpack.exit_code !== 0) return { error: 'Xuda could not unpack that repository on the machine.' };
196
+
197
+ if (!has_repo) {
198
+ // `git clone <bundle> .` refuses a non-empty directory, so init and fetch instead, which
199
+ // also leaves the bundle as a named remote we control.
200
+ const init = await workspace.exec(['sh', '-c', 'git init -q . && git remote remove origin 2>/dev/null; git remote add origin .xucode_repo.bundle'], { timeout_ms: 120000 });
201
+ if (init.exit_code !== 0) return { error: 'Xuda could not prepare a working copy on the machine.' };
202
+ } else {
203
+ await workspace.exec(['sh', '-c', 'git remote remove origin 2>/dev/null; git remote add origin .xucode_repo.bundle'], { timeout_ms: 60000 });
204
+ }
205
+
206
+ const fetched = await workspace.exec(['sh', '-c', 'git fetch -q origin "+refs/heads/*:refs/remotes/origin/*"'], { timeout_ms: CLONE_TIMEOUT_MS });
207
+ if (fetched.exit_code !== 0) {
208
+ console.error(`[xucode] workspace fetch from bundle failed: ${(fetched.stderr || '').slice(-200)}`);
209
+ return { error: 'Xuda could not read that repository on the machine.' };
210
+ }
211
+
212
+ if (branch) {
213
+ // Same contract as everywhere else: the chat owns a branch, the default branch is never
214
+ // checked out for writing. Reuse the branch when the workspace already has it rather than
215
+ // resetting onto the remote, or a warm machine loses whatever the last run left dirty.
216
+ const local = await workspace.exec(['sh', '-c', `git rev-parse --verify refs/heads/${branch}`], { timeout_ms: 60000 });
217
+ if (local.exit_code === 0) {
218
+ const co = await workspace.exec(['sh', '-c', `git checkout -q ${branch}`], { timeout_ms: 120000 });
219
+ if (co.exit_code !== 0) return { error: 'Xuda could not switch to this chat branch on the machine.' };
220
+ } else {
221
+ const on_remote = await workspace.exec(['sh', '-c', `git rev-parse --verify refs/remotes/origin/${branch}`], { timeout_ms: 60000 });
222
+ const from = on_remote.exit_code === 0 ? `origin/${branch}` : `origin/${default_branch}`;
223
+ const made_branch = await workspace.exec(['sh', '-c', `git checkout -q -B ${branch} ${from} || git checkout -q -B ${branch}`], { timeout_ms: 120000 });
224
+ if (made_branch.exit_code !== 0) return { error: 'Xuda could not create a branch on the machine.' };
225
+ }
226
+ }
227
+
228
+ const head = await workspace.exec(['sh', '-c', 'git rev-parse HEAD'], { timeout_ms: 60000 });
229
+ return { dir: workspace.dir, branch: branch || null, base_sha: (head.stdout || '').trim() || null, reused: has_repo };
230
+ } finally {
231
+ await fs.promises.rm(bundle, { force: true }).catch(() => {});
232
+ }
233
+ },
234
+
235
+ // The return trip. The workspace bundles whatever the run produced, the bundle crosses back as
236
+ // base64, and the MIRROR fetches from it. Only then does the mirror push upstream, which is the
237
+ // one authenticated step and it still happens on the region server.
238
+ async collect_from_workspace({ workspace, uid, repo, branch }) {
239
+ if (!branch) return { error: 'no branch to collect' };
240
+ // -w0 is GNU-only, and BSD wraps at 76 columns with no way to turn it off, so the newlines
241
+ // are stripped afterwards instead. Works on both.
242
+ const made = await workspace.exec(['sh', '-c', `git bundle create .xucode_out.bundle ${branch} && base64 < .xucode_out.bundle | tr -d '\n'`], { timeout_ms: CLONE_TIMEOUT_MS });
243
+ if (made.exit_code !== 0) {
244
+ console.error(`[xucode] could not bundle ${branch} on the machine: ${(made.stderr || '').slice(-200)}`);
245
+ return { error: 'Xuda could not package your changes.' };
246
+ }
247
+ const bundle = path.join(os.tmpdir(), `xucode-in-${repo._id}-${Date.now()}.bundle`);
248
+ try {
249
+ await fs.promises.writeFile(bundle, Buffer.from((made.stdout || '').trim(), 'base64'));
250
+ const dir = mirror_dir(uid, repo._id);
251
+ const fetched = await git_exec({ cwd: dir, args: ['fetch', bundle, `+refs/heads/${branch}:refs/heads/${branch}`], timeout_ms: CLONE_TIMEOUT_MS });
252
+ if (fetched.exit_code !== 0) {
253
+ console.error(`[xucode] mirror could not fetch ${branch} from the workspace bundle: exit ${fetched.exit_code}`);
254
+ return { error: 'Xuda could not save your changes.' };
255
+ }
256
+ return { collected: true, branch };
257
+ } finally {
258
+ await fs.promises.rm(bundle, { force: true }).catch(() => {});
259
+ await workspace.exec(['sh', '-c', 'rm -f .xucode_out.bundle'], { timeout_ms: 60000 }).catch(() => {});
260
+ }
261
+ },
262
+
156
263
  // Workspace to mirror. Runs with the workspace's own git, over a path or a VPN url, and never
157
264
  // sees a credential.
158
265
  async push_from_workspace({ work_dir, mirror_dir: to, branch }) {
159
266
  if (!branch) return { error: 'no branch to push' };
160
267
  const pushed = await git_exec({ cwd: work_dir, args: ['push', to, `refs/heads/${branch}:refs/heads/${branch}`], timeout_ms: CLONE_TIMEOUT_MS });
161
268
  if (pushed.exit_code !== 0) {
162
- console.error(`[xudex] workspace push to mirror failed for ${branch}: exit ${pushed.exit_code}`);
269
+ console.error(`[xucode] workspace push to mirror failed for ${branch}: exit ${pushed.exit_code}`);
163
270
  return { error: 'Xuda could not save that branch.' };
164
271
  }
165
272
  return { pushed: true, branch };
@@ -184,7 +291,7 @@ export const create_mirror = function ({ git_exec, du_mb, mirror_root, clone_tim
184
291
  timeout_ms: CLONE_TIMEOUT_MS,
185
292
  });
186
293
  if (pushed.exit_code !== 0) {
187
- console.error(`[xudex] mirror push failed for ${repo._id} ${branch}: exit ${pushed.exit_code}`);
294
+ console.error(`[xucode] mirror push failed for ${repo._id} ${branch}: exit ${pushed.exit_code}`);
188
295
  return { error: 'Xuda could not push that branch to your repository.' };
189
296
  }
190
297
  return { pushed: true, branch };
@@ -1,6 +1,6 @@
1
- // Xudex preview: the project, actually running.
1
+ // Xucode preview: the project, actually running.
2
2
  //
3
- // docs/plans/xudex.md section 7, and the last gate of the verify loop in section 6. The verify
3
+ // docs/plans/xucode.md section 7, and the last gate of the verify loop in section 6. The verify
4
4
  // loop already left the seam open (`runtime_check`); this is what plugs into it.
5
5
  //
6
6
  // ── Why this is a gate and not a feature ───────────────────────────────────────────────
@@ -27,7 +27,7 @@ if (!global._conf) {
27
27
  global._conf = (await import(path.join(process.env.XUDA_HOME, 'common', 'load_conf.mjs'))).loadConf();
28
28
  }
29
29
 
30
- const preview_conf = () => global._conf.xudex?.preview || {};
30
+ const preview_conf = () => global._conf.xucode?.preview || {};
31
31
  const num = (key, fallback) => Number(preview_conf()[key]) || fallback;
32
32
 
33
33
  // Where a project's dev server is told to listen. Fixed per project rather than random, so a
@@ -111,8 +111,8 @@ export const extract_errors = function (log, max = num('max_errors', 8)) {
111
111
  const shq = (value) => `'${String(value).replace(/'/g, `'\\''`)}'`;
112
112
 
113
113
  export const create_preview = function ({ conf = preview_conf, now = () => Date.now() } = {}) {
114
- const log_path = (project_id) => `/tmp/xudex-preview-${String(project_id).replace(/[^a-zA-Z0-9_-]/g, '')}.log`;
115
- const pid_path = (project_id) => `/tmp/xudex-preview-${String(project_id).replace(/[^a-zA-Z0-9_-]/g, '')}.pid`;
114
+ const log_path = (project_id) => `/tmp/xucode-preview-${String(project_id).replace(/[^a-zA-Z0-9_-]/g, '')}.log`;
115
+ const pid_path = (project_id) => `/tmp/xucode-preview-${String(project_id).replace(/[^a-zA-Z0-9_-]/g, '')}.pid`;
116
116
 
117
117
  const is_listening = async function (workspace, port) {
118
118
  // Node is guaranteed present (this is a Node project by definition) and curl is not, so the