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
|
@@ -242,9 +242,16 @@ function codexConfigDirs(home) {
|
|
|
242
242
|
});
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
function
|
|
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
|
|
261
|
-
if (
|
|
262
|
-
return
|
|
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,
|