amalgm 0.1.66 → 0.1.68

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.66",
3
+ "version": "0.1.68",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -126,11 +126,6 @@ function runtimeHome({ amalgmDir, harness, authProfileId }) {
126
126
  return path.join(amalgmDir, 'cli-homes', safePathSegment(harness), safePathSegment(authProfileId));
127
127
  }
128
128
 
129
- function nativeProviderHome(harness) {
130
- if (harness === 'claude_code') return os.homedir();
131
- return null;
132
- }
133
-
134
129
  function pinnedRuntimeHome(amalgmDir, cliHomePath) {
135
130
  if (typeof cliHomePath !== 'string' || !cliHomePath.trim()) return null;
136
131
  const root = path.resolve(amalgmDir);
@@ -183,8 +178,7 @@ function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken
183
178
  tokenFingerprint,
184
179
  authProfileId: profileId,
185
180
  runtimeHome:
186
- nativeProviderHome(method === 'provider_auth' ? harness : null)
187
- || pinnedRuntimeHome(amalgmDir, cliHomePath)
181
+ pinnedRuntimeHome(amalgmDir, cliHomePath)
188
182
  || runtimeHome({ amalgmDir, harness, authProfileId: profileId }),
189
183
  };
190
184
  }
@@ -202,15 +196,6 @@ function runtimeEnv(contract, baseEnv = process.env) {
202
196
  for (const key of ['OPENAI_API_KEY', 'OPENAI_BASE_URL', 'ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'AI_GATEWAY_API_KEY', 'AI_GATEWAY_BASE_URL']) {
203
197
  delete env[key];
204
198
  }
205
- if (contract.harness === 'claude_code' && contract.authMethod === 'provider_auth') {
206
- env.HOME = os.homedir();
207
- delete env.CLAUDE_CONFIG_DIR;
208
- return {
209
- ...env,
210
- IS_SANDBOX: '1',
211
- ...(baseEnv.AMALGM_RUNTIME_TOKEN ? { AMALGM_RUNTIME_TOKEN: baseEnv.AMALGM_RUNTIME_TOKEN } : {}),
212
- };
213
- }
214
199
  if (contract.auth.runtimeHome) {
215
200
  fs.mkdirSync(contract.auth.runtimeHome, { recursive: true });
216
201
  env.HOME = contract.auth.runtimeHome;
@@ -218,6 +203,9 @@ function runtimeEnv(contract, baseEnv = process.env) {
218
203
  env.CLAUDE_CONFIG_DIR = contract.auth.runtimeHome;
219
204
  env.OPENCODE_HOME = contract.auth.runtimeHome;
220
205
  env.OPENCODE_CONFIG_DIR = contract.auth.runtimeHome;
206
+ if (contract.harness === 'claude_code' && contract.authMethod === 'provider_auth') {
207
+ delete env.CLAUDE_CONFIG_DIR;
208
+ }
221
209
  }
222
210
  env.IS_SANDBOX = '1';
223
211
  if (baseEnv.AMALGM_RUNTIME_TOKEN) env.AMALGM_RUNTIME_TOKEN = baseEnv.AMALGM_RUNTIME_TOKEN;
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const assert = require('node:assert/strict');
4
- const os = require('node:os');
5
4
  const test = require('node:test');
6
5
  const { authEnvelope, runtimeEnv } = require('../auth');
7
6
 
@@ -63,7 +62,7 @@ test('opencode amalgm auth uses a pinned CLI home across sessions and proxy toke
63
62
  assert.equal(env.OPENCODE_CONFIG_DIR, first.runtimeHome);
64
63
  });
65
64
 
66
- test('claude provider auth uses the native user home with CLAUDE_CONFIG_DIR unset', () => {
65
+ test('claude provider auth uses a pinned CLI home with native config copied separately', () => {
67
66
  const envelope = authEnvelope({
68
67
  harness: 'claude_code',
69
68
  authMethod: 'provider_auth',
@@ -71,7 +70,7 @@ test('claude provider auth uses the native user home with CLAUDE_CONFIG_DIR unse
71
70
  amalgmDir: '/tmp/amalgm-test',
72
71
  });
73
72
 
74
- assert.equal(envelope.runtimeHome, os.homedir());
73
+ assert.match(envelope.runtimeHome, /\/tmp\/amalgm-test\/cli-homes\/claude_code\/provider-[0-9a-f]{16}$/);
75
74
 
76
75
  const env = runtimeEnv({
77
76
  harness: 'claude_code',
@@ -84,7 +83,7 @@ test('claude provider auth uses the native user home with CLAUDE_CONFIG_DIR unse
84
83
  ANTHROPIC_API_KEY: 'should-not-leak',
85
84
  });
86
85
 
87
- assert.equal(env.HOME, os.homedir());
86
+ assert.equal(env.HOME, envelope.runtimeHome);
88
87
  assert.equal(env.CLAUDE_CONFIG_DIR, undefined);
89
88
  assert.equal(env.ANTHROPIC_API_KEY, undefined);
90
89
  });
@@ -30,7 +30,7 @@ function withNativeHome(fn) {
30
30
  }
31
31
  }
32
32
 
33
- test('codex native sync copies hook support without bulk runtime state', () => {
33
+ test('codex native sync copies config without deleting existing runtime state', () => {
34
34
  withNativeHome((home) => {
35
35
  const source = path.join(home, '.codex');
36
36
  fs.mkdirSync(path.join(source, 'sessions'), { recursive: true });
@@ -58,7 +58,8 @@ test('codex native sync copies hook support without bulk runtime state', () => {
58
58
  assert.equal(fs.existsSync(path.join(runtimeHome, 'hooks.json')), true);
59
59
  assert.equal(fs.existsSync(path.join(runtimeHome, 'supermemory.json')), true);
60
60
  assert.equal(fs.existsSync(path.join(runtimeHome, 'supermemory', 'recall.js')), true);
61
- assert.equal(fs.existsSync(path.join(runtimeHome, 'sessions')), false);
61
+ assert.equal(fs.existsSync(path.join(runtimeHome, 'sessions', 'legacy.jsonl')), true);
62
+ assert.equal(fs.existsSync(path.join(runtimeHome, 'sessions', 'huge.jsonl')), false);
62
63
  assert.equal(fs.existsSync(path.join(runtimeHome, 'worktrees')), false);
63
64
  assert.equal(fs.existsSync(path.join(runtimeHome, 'plugins')), false);
64
65
  assert.equal(fs.existsSync(path.join(runtimeHome, '.codex', 'hooks.json')), true);
@@ -195,3 +196,23 @@ test('claude native sync skips debug symlinks', () => {
195
196
  assert.equal(fs.existsSync(path.join(runtimeHome, 'debug', 'latest')), false);
196
197
  });
197
198
  });
199
+
200
+ test('claude native sync links native keychains on macOS', () => {
201
+ withNativeHome((home) => {
202
+ const source = path.join(home, '.claude');
203
+ fs.mkdirSync(source, { recursive: true });
204
+ fs.writeFileSync(path.join(source, 'settings.json'), '{"hooks":{}}');
205
+ fs.mkdirSync(path.join(home, 'Library', 'Keychains'), { recursive: true });
206
+
207
+ const runtimeHome = path.join(home, 'runtime-home');
208
+ syncClaudeNativeConfig(runtimeHome);
209
+
210
+ const keychains = path.join(runtimeHome, 'Library', 'Keychains');
211
+ if (process.platform === 'darwin') {
212
+ assert.equal(fs.lstatSync(keychains).isSymbolicLink(), true);
213
+ assert.equal(fs.realpathSync(keychains), fs.realpathSync(path.join(home, 'Library', 'Keychains')));
214
+ } else {
215
+ assert.equal(fs.existsSync(keychains), false);
216
+ }
217
+ });
218
+ });
@@ -120,16 +120,30 @@ function removeOutboundSymlink(root, relativePath) {
120
120
  }
121
121
  }
122
122
 
123
- function nativeHome() {
124
- return process.env.AMALGM_NATIVE_HOME || os.homedir();
123
+ function ensureNativeKeychainAlias(runtimeHome) {
124
+ if (process.platform !== 'darwin' || !runtimeHome) return false;
125
+ const source = path.join(nativeHome(), 'Library', 'Keychains');
126
+ if (!exists(source)) return false;
127
+ const target = path.join(runtimeHome, 'Library', 'Keychains');
128
+ try {
129
+ const stat = fs.lstatSync(target);
130
+ if (stat.isSymbolicLink()) {
131
+ const linkTarget = fs.readlinkSync(target);
132
+ if (path.resolve(path.dirname(target), linkTarget) === path.resolve(source)) return true;
133
+ }
134
+ fs.rmSync(target, { recursive: true, force: true });
135
+ } catch {}
136
+ fs.mkdirSync(path.dirname(target), { recursive: true });
137
+ try {
138
+ fs.symlinkSync(source, target, 'dir');
139
+ return true;
140
+ } catch {
141
+ return false;
142
+ }
125
143
  }
126
144
 
127
- function pruneLegacyCodexRuntimeHome(runtimeHome) {
128
- for (const name of EXCLUDED_DIR_NAMES) {
129
- try {
130
- fs.rmSync(path.join(runtimeHome, name), { recursive: true, force: true });
131
- } catch {}
132
- }
145
+ function nativeHome() {
146
+ return process.env.AMALGM_NATIVE_HOME || os.homedir();
133
147
  }
134
148
 
135
149
  function copyDirBounded(sourceDir, targetDir, options = {}) {
@@ -178,7 +192,6 @@ function syncCodexNativeConfig(runtimeHome) {
178
192
  fs.mkdirSync(runtimeHome, { recursive: true });
179
193
  if (!exists(sourceDir)) return null;
180
194
 
181
- pruneLegacyCodexRuntimeHome(runtimeHome);
182
195
  const nativeConfig = copyDirBounded(sourceDir, runtimeHome);
183
196
  const copiedFiles = [
184
197
  copyFileIfPresent(path.join(sourceDir, 'config.toml'), path.join(runtimeHome, 'config.toml')),
@@ -208,6 +221,7 @@ function syncClaudeNativeConfig(runtimeHome) {
208
221
  removeOutboundSymlink(runtimeHome, path.join('debug', 'latest'));
209
222
  const copied = copyConfigTree(sourceDir, runtimeHome);
210
223
  ensureHomeAlias(runtimeHome, '.claude');
224
+ ensureNativeKeychainAlias(runtimeHome);
211
225
  copyFileIfPresent(path.join(home, '.claude.json'), path.join(runtimeHome, '.claude.json'));
212
226
  copyConfigTree(path.join(home, '.config', 'claude'), path.join(runtimeHome, '.config', 'claude'));
213
227
  return copied ? { sourceDir, runtimeHome } : null;
@@ -268,6 +282,7 @@ module.exports = {
268
282
  copyConfigTree,
269
283
  copyDirBounded,
270
284
  copyFileIfPresent,
285
+ ensureNativeKeychainAlias,
271
286
  ensureHomeAlias,
272
287
  shouldCopyConfigPath,
273
288
  },