amalgm 0.1.82 → 0.1.83

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.82",
3
+ "version": "0.1.83",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -242,9 +242,16 @@ function codexConfigDirs(home) {
242
242
  });
243
243
  }
244
244
 
245
- function writeCodexHomeFile(home, filename, contents, mode = 0o600) {
245
+ function unlinkSymlinkIfPresent(target) {
246
+ try {
247
+ if (fs.lstatSync(target).isSymbolicLink()) fs.unlinkSync(target);
248
+ } catch {}
249
+ }
250
+
251
+ function writeCodexHomeFile(home, filename, contents, mode = 0o600, options = {}) {
246
252
  for (const dir of codexConfigDirs(home)) {
247
253
  const target = path.join(dir, filename);
254
+ if (options.unlinkSymlink) unlinkSymlinkIfPresent(target);
248
255
  fs.writeFileSync(target, contents, { mode });
249
256
  try { fs.chmodSync(target, mode); } catch {}
250
257
  }
@@ -441,10 +448,6 @@ function writeConfig(contract, syncInfo) {
441
448
  writeCodexHomeFile(home, 'hooks.json', toCodexHooksJson(contract.agentConfig), 0o600);
442
449
  }
443
450
  if (contract.authMethod === 'provider_auth') {
444
- const sourceAuth = path.join(syncInfo?.sourceDir || path.join(os.homedir(), '.codex'), 'auth.json');
445
- if (fs.existsSync(sourceAuth)) {
446
- writeCodexHomeFile(home, 'auth.json', fs.readFileSync(sourceAuth), 0o600);
447
- }
448
451
  writeCodexHomeFile(home, 'config.toml', buildCodexConfig(contract, nativeConfig, syncInfo), 0o600);
449
452
  return;
450
453
  }
@@ -452,7 +455,7 @@ function writeConfig(contract, syncInfo) {
452
455
  writeCodexHomeFile(home, 'auth.json', JSON.stringify({
453
456
  auth_mode: 'apikey',
454
457
  OPENAI_API_KEY: contract.auth.tokenRef,
455
- }, null, 2), 0o600);
458
+ }, null, 2), 0o600, { unlinkSymlink: true });
456
459
  }
457
460
 
458
461
  class CodexAdapter {
@@ -11,6 +11,7 @@ const {
11
11
  claudeNativeHookSettings,
12
12
  syncClaudeNativeConfig,
13
13
  syncCodexNativeConfig,
14
+ syncCodexProviderAuth,
14
15
  syncOpenCodeNativeConfig,
15
16
  } = require('../tooling/native-config');
16
17
  const { prepareHarnessRuntime } = require('../tooling/runtime-home');
@@ -32,6 +33,12 @@ function withNativeHome(fn) {
32
33
  }
33
34
  }
34
35
 
36
+ function sameFile(a, b) {
37
+ const left = fs.statSync(a);
38
+ const right = fs.statSync(b);
39
+ return left.dev === right.dev && left.ino === right.ino;
40
+ }
41
+
35
42
  test('codex native sync copies config without deleting existing runtime state', () => {
36
43
  withNativeHome((home) => {
37
44
  const source = path.join(home, '.codex');
@@ -42,6 +49,7 @@ test('codex native sync copies config without deleting existing runtime state',
42
49
  fs.writeFileSync(path.join(source, 'config.toml'), 'model = "gpt-5.5"');
43
50
  fs.writeFileSync(path.join(source, 'preferences.json'), '{"theme":"dark"}');
44
51
  fs.writeFileSync(path.join(source, 'hooks.json'), '{"hooks":{}}');
52
+ fs.writeFileSync(path.join(source, 'auth.json'), '{"auth_mode":"chatgpt"}');
45
53
  fs.writeFileSync(path.join(source, 'supermemory.json'), '{"projectContainerTag":"test"}');
46
54
  fs.writeFileSync(path.join(source, 'supermemory', 'recall.js'), 'console.log("recall")');
47
55
  fs.writeFileSync(path.join(source, 'sessions', 'huge.jsonl'), 'nope');
@@ -65,6 +73,9 @@ test('codex native sync copies config without deleting existing runtime state',
65
73
  assert.equal(fs.existsSync(path.join(runtimeHome, 'worktrees')), false);
66
74
  assert.equal(fs.existsSync(path.join(runtimeHome, 'plugins')), false);
67
75
  assert.equal(fs.existsSync(path.join(runtimeHome, '.codex', 'hooks.json')), true);
76
+ assert.equal(result.linkedAuth, true);
77
+ assert.equal(sameFile(path.join(source, 'auth.json'), path.join(runtimeHome, 'auth.json')), true);
78
+ assert.equal(sameFile(path.join(source, 'auth.json'), path.join(runtimeHome, '.codex', 'auth.json')), true);
68
79
  });
69
80
  });
70
81
 
@@ -88,8 +99,12 @@ test('prepared codex runtime imports provider auth without native config prefs',
88
99
  assert.equal(prepared.env.CODEX_HOME, runtimeHome);
89
100
  assert.equal(prepared.syncInfo.runtimeHome, runtimeHome);
90
101
  assert.equal(prepared.syncInfo.authOnly, true);
102
+ assert.equal(prepared.syncInfo.linkedAuth, true);
103
+ assert.equal(prepared.syncInfo.copied, false);
91
104
  assert.equal(fs.existsSync(path.join(runtimeHome, 'auth.json')), true);
92
105
  assert.equal(fs.existsSync(path.join(runtimeHome, '.codex', 'auth.json')), true);
106
+ assert.equal(sameFile(path.join(source, 'auth.json'), path.join(runtimeHome, 'auth.json')), true);
107
+ assert.equal(sameFile(path.join(source, 'auth.json'), path.join(runtimeHome, '.codex', 'auth.json')), true);
93
108
  assert.equal(fs.existsSync(path.join(runtimeHome, 'config.toml')), false);
94
109
  assert.equal(fs.existsSync(path.join(runtimeHome, 'hooks.json')), false);
95
110
  assert.equal(fs.existsSync(path.join(runtimeHome, '.codex', 'config.toml')), false);
@@ -205,6 +220,7 @@ test('codex provider config ignores native prefs and renders agent hooks', () =>
205
220
  assert.equal(config.includes(path.join(runtimeHome, 'hooks.json')), true);
206
221
  const hooks = fs.readFileSync(path.join(runtimeHome, 'hooks.json'), 'utf8');
207
222
  assert.match(hooks, /echo managed/);
223
+ assert.equal(sameFile(path.join(source, 'auth.json'), path.join(runtimeHome, 'auth.json')), true);
208
224
  });
209
225
  });
210
226
 
@@ -273,6 +289,36 @@ test('codex amalgm config overwrites stale real dot-codex auth files', () => {
273
289
  });
274
290
  });
275
291
 
292
+ test('codex amalgm config breaks provider auth link before writing managed auth', () => {
293
+ withNativeHome((home) => {
294
+ const source = path.join(home, '.codex');
295
+ fs.mkdirSync(source, { recursive: true });
296
+ fs.writeFileSync(path.join(source, 'auth.json'), JSON.stringify({
297
+ auth_mode: 'chatgpt',
298
+ tokens: { refresh_token: 'native-refresh' },
299
+ }));
300
+
301
+ const runtimeHome = path.join(home, 'runtime-home');
302
+ const syncInfo = syncCodexProviderAuth(runtimeHome);
303
+ assert.equal(syncInfo.linkedAuth, true);
304
+ assert.equal(sameFile(path.join(source, 'auth.json'), path.join(runtimeHome, 'auth.json')), true);
305
+
306
+ codexPrivate.writeConfig({
307
+ authMethod: 'amalgm',
308
+ auth: { runtimeHome, baseUrl: 'https://amalgm.example/v1', tokenRef: 'test-token' },
309
+ mcpServers: [],
310
+ });
311
+
312
+ const nativeAuth = JSON.parse(fs.readFileSync(path.join(source, 'auth.json'), 'utf8'));
313
+ const runtimeAuth = JSON.parse(fs.readFileSync(path.join(runtimeHome, 'auth.json'), 'utf8'));
314
+ assert.equal(nativeAuth.auth_mode, 'chatgpt');
315
+ assert.equal(nativeAuth.tokens.refresh_token, 'native-refresh');
316
+ assert.equal(runtimeAuth.auth_mode, 'apikey');
317
+ assert.equal(runtimeAuth.OPENAI_API_KEY, 'test-token');
318
+ assert.equal(sameFile(path.join(source, 'auth.json'), path.join(runtimeHome, 'auth.json')), false);
319
+ });
320
+ });
321
+
276
322
  test('claude renders agent hooks without importing native permission settings', () => {
277
323
  withNativeHome((home) => {
278
324
  const source = path.join(home, '.claude');
@@ -54,6 +54,16 @@ function shouldCopyConfigPath(root, source) {
54
54
  return !EXCLUDED_FILE_PATTERNS.some((pattern) => pattern.test(basename));
55
55
  }
56
56
 
57
+ function sameFile(a, b) {
58
+ try {
59
+ const left = fs.statSync(a);
60
+ const right = fs.statSync(b);
61
+ return left.dev === right.dev && left.ino === right.ino;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
57
67
  function copyConfigTree(sourceDir, targetDir) {
58
68
  if (!sourceDir || !targetDir || !exists(sourceDir)) return false;
59
69
  if (path.resolve(sourceDir) === path.resolve(targetDir)) return true;
@@ -63,6 +73,7 @@ function copyConfigTree(sourceDir, targetDir) {
63
73
  function copyFileIfPresent(source, target) {
64
74
  if (!exists(source)) return false;
65
75
  if (!ensureDirectory(path.dirname(target)) || !canWriteFileTarget(target)) return false;
76
+ unlinkSymlinkIfPresent(target);
66
77
  fs.copyFileSync(source, target);
67
78
  try {
68
79
  fs.chmodSync(target, fs.statSync(source).mode & 0o777);
@@ -70,6 +81,33 @@ function copyFileIfPresent(source, target) {
70
81
  return true;
71
82
  }
72
83
 
84
+ function unlinkSymlinkIfPresent(target) {
85
+ try {
86
+ if (fs.lstatSync(target).isSymbolicLink()) fs.unlinkSync(target);
87
+ } catch {}
88
+ }
89
+
90
+ function linkFileIfPresent(source, target) {
91
+ if (!exists(source)) return false;
92
+ if (!ensureDirectory(path.dirname(target))) return false;
93
+ if (sameFile(source, target)) return true;
94
+ try {
95
+ const stat = fs.lstatSync(target);
96
+ if (stat.isDirectory()) return false;
97
+ fs.unlinkSync(target);
98
+ } catch {}
99
+ try {
100
+ fs.symlinkSync(source, target, 'file');
101
+ return true;
102
+ } catch {}
103
+ try {
104
+ fs.linkSync(source, target);
105
+ return true;
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
73
111
  function ensureHomeAlias(runtimeHome, dotDirName) {
74
112
  if (!runtimeHome || !dotDirName) return;
75
113
  const alias = path.join(runtimeHome, dotDirName);
@@ -141,10 +179,13 @@ function copyDirBounded(sourceDir, targetDir, options = {}) {
141
179
  if (!sourceDir || !targetDir || !exists(sourceDir)) return { copied: false, files: 0, bytes: 0, truncated: false };
142
180
  const maxFiles = Number(options.maxFiles || 200);
143
181
  const maxBytes = Number(options.maxBytes || 10 * 1024 * 1024);
182
+ const skipRelatives = new Set(Array.isArray(options.skipRelatives) ? options.skipRelatives : []);
144
183
  const state = { copied: false, files: 0, bytes: 0, truncated: false };
145
184
  const root = path.resolve(sourceDir);
146
185
 
147
186
  function walk(source, target) {
187
+ const relative = path.relative(root, source);
188
+ if (relative && skipRelatives.has(relative)) return;
148
189
  if (state.truncated || !shouldCopyConfigPath(root, source)) return;
149
190
  let stat;
150
191
  try {
@@ -180,26 +221,29 @@ function copyDirBounded(sourceDir, targetDir, options = {}) {
180
221
  function syncCodexNativeConfig(runtimeHome) {
181
222
  if (!runtimeHome) return null;
182
223
  const sourceDir = path.join(nativeHome(), '.codex');
224
+ const sourceAuth = path.join(sourceDir, 'auth.json');
183
225
  fs.mkdirSync(runtimeHome, { recursive: true });
184
226
  if (!exists(sourceDir)) return null;
185
227
 
186
- const nativeConfig = copyDirBounded(sourceDir, runtimeHome);
228
+ const nativeConfig = copyDirBounded(sourceDir, runtimeHome, { skipRelatives: ['auth.json'] });
187
229
  const copiedFiles = [
188
230
  copyFileIfPresent(path.join(sourceDir, 'config.toml'), path.join(runtimeHome, 'config.toml')),
189
- copyFileIfPresent(path.join(sourceDir, 'auth.json'), path.join(runtimeHome, 'auth.json')),
190
231
  copyFileIfPresent(path.join(sourceDir, 'hooks.json'), path.join(runtimeHome, 'hooks.json')),
191
232
  copyFileIfPresent(path.join(sourceDir, 'supermemory.json'), path.join(runtimeHome, 'supermemory.json')),
192
233
  ].filter(Boolean).length;
193
234
  const supermemory = copyDirBounded(path.join(sourceDir, 'supermemory'), path.join(runtimeHome, 'supermemory'));
194
235
  copyDirBounded(path.join(nativeHome(), '.codex-supermemory'), path.join(runtimeHome, '.codex-supermemory'));
195
236
  ensureHomeAlias(runtimeHome, '.codex');
237
+ const linkedAuth = linkFileIfPresent(sourceAuth, path.join(runtimeHome, 'auth.json'));
238
+ linkFileIfPresent(sourceAuth, path.join(runtimeHome, '.codex', 'auth.json'));
196
239
 
197
240
  return {
198
241
  sourceDir,
199
242
  runtimeHome,
200
243
  sourceConfigPath: path.join(sourceDir, 'config.toml'),
201
- copied: nativeConfig.copied || copiedFiles + (supermemory.copied ? 1 : 0) > 0,
244
+ copied: nativeConfig.copied || copiedFiles + (supermemory.copied ? 1 : 0) > 0 || linkedAuth,
202
245
  truncated: nativeConfig.truncated || supermemory.truncated,
246
+ linkedAuth,
203
247
  };
204
248
  }
205
249
 
@@ -255,11 +299,12 @@ function copyJsonSubsetIfPresent(source, target, keys) {
255
299
  function syncCodexProviderAuth(runtimeHome) {
256
300
  if (!runtimeHome) return null;
257
301
  const sourceDir = path.join(nativeHome(), '.codex');
302
+ const sourceAuth = path.join(sourceDir, 'auth.json');
258
303
  fs.mkdirSync(runtimeHome, { recursive: true });
259
304
  ensureHomeAlias(runtimeHome, '.codex');
260
- const copied = copyFileIfPresent(path.join(sourceDir, 'auth.json'), path.join(runtimeHome, 'auth.json'));
261
- if (copied) copyFileIfPresent(path.join(sourceDir, 'auth.json'), path.join(runtimeHome, '.codex', 'auth.json'));
262
- return copied ? { sourceDir, runtimeHome, copied: true, authOnly: true } : null;
305
+ const linked = linkFileIfPresent(sourceAuth, path.join(runtimeHome, 'auth.json'));
306
+ if (linked) linkFileIfPresent(sourceAuth, path.join(runtimeHome, '.codex', 'auth.json'));
307
+ return linked ? { sourceDir, runtimeHome, copied: false, linkedAuth: true, authOnly: true } : null;
263
308
  }
264
309
 
265
310
  function syncClaudeProviderAuth(runtimeHome) {
@@ -367,7 +412,9 @@ module.exports = {
367
412
  ensureDirectory,
368
413
  ensureNativeKeychainAlias,
369
414
  ensureHomeAlias,
415
+ linkFileIfPresent,
370
416
  shouldCopyConfigPath,
417
+ unlinkSymlinkIfPresent,
371
418
  },
372
419
  claudeNativeHookSettings,
373
420
  syncClaudeProviderAuth,