amalgm 0.0.0 → 0.0.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 +37 -1
- package/bin/amalgm.js +8 -2
- package/lib/auth-store.js +223 -0
- package/lib/cli.js +1000 -0
- package/lib/paths.js +30 -0
- package/lib/supervisor.js +467 -0
- package/lib/tunnel-chat.js +328 -0
- package/lib/tunnel-events.js +499 -0
- package/package.json +29 -3
- package/runtime/README.md +4 -0
- package/runtime/lib/chatInput.js +306 -0
- package/runtime/lib/harnesses.js +988 -0
- package/runtime/lib/local/amalgmStore.js +128 -0
- package/runtime/lib/local/credentialResolver.js +425 -0
- package/runtime/lib/mcpApps/registry.js +619 -0
- package/runtime/package.json +5 -0
- package/runtime/scripts/amalgm-mcp/agents/rest.js +165 -0
- package/runtime/scripts/amalgm-mcp/agents/store.js +153 -0
- package/runtime/scripts/amalgm-mcp/agents/talk.js +1156 -0
- package/runtime/scripts/amalgm-mcp/agents/tools.js +210 -0
- package/runtime/scripts/amalgm-mcp/artifacts/advertise.js +132 -0
- package/runtime/scripts/amalgm-mcp/artifacts/rest.js +103 -0
- package/runtime/scripts/amalgm-mcp/artifacts/store.js +141 -0
- package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +402 -0
- package/runtime/scripts/amalgm-mcp/artifacts/tools.js +176 -0
- package/runtime/scripts/amalgm-mcp/browser/page.js +637 -0
- package/runtime/scripts/amalgm-mcp/browser/tools.js +688 -0
- package/runtime/scripts/amalgm-mcp/config.js +138 -0
- package/runtime/scripts/amalgm-mcp/credentials/rest.js +45 -0
- package/runtime/scripts/amalgm-mcp/deps.js +40 -0
- package/runtime/scripts/amalgm-mcp/email/inbound.js +215 -0
- package/runtime/scripts/amalgm-mcp/events/executor.js +179 -0
- package/runtime/scripts/amalgm-mcp/events/ingress.js +113 -0
- package/runtime/scripts/amalgm-mcp/events/matcher.js +125 -0
- package/runtime/scripts/amalgm-mcp/events/rest.js +200 -0
- package/runtime/scripts/amalgm-mcp/events/ring-buffer.js +19 -0
- package/runtime/scripts/amalgm-mcp/events/store.js +98 -0
- package/runtime/scripts/amalgm-mcp/events/tools.js +306 -0
- package/runtime/scripts/amalgm-mcp/events/webhook-url.js +28 -0
- package/runtime/scripts/amalgm-mcp/fs/rest.js +293 -0
- package/runtime/scripts/amalgm-mcp/index.js +100 -0
- package/runtime/scripts/amalgm-mcp/lib/chat-runner.js +167 -0
- package/runtime/scripts/amalgm-mcp/lib/email-md.js +288 -0
- package/runtime/scripts/amalgm-mcp/lib/mcp-resolver.js +63 -0
- package/runtime/scripts/amalgm-mcp/lib/prefs.js +393 -0
- package/runtime/scripts/amalgm-mcp/lib/storage.js +92 -0
- package/runtime/scripts/amalgm-mcp/lib/supabase.js +118 -0
- package/runtime/scripts/amalgm-mcp/lib/tool-result.js +177 -0
- package/runtime/scripts/amalgm-mcp/local/rest.js +80 -0
- package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +151 -0
- package/runtime/scripts/amalgm-mcp/notify/index.js +107 -0
- package/runtime/scripts/amalgm-mcp/server/http.js +335 -0
- package/runtime/scripts/amalgm-mcp/server/mcp.js +116 -0
- package/runtime/scripts/amalgm-mcp/slack/inbound.js +200 -0
- package/runtime/scripts/amalgm-mcp/tasks/executor.js +225 -0
- package/runtime/scripts/amalgm-mcp/tasks/rest.js +110 -0
- package/runtime/scripts/amalgm-mcp/tasks/schedule-normalization.js +85 -0
- package/runtime/scripts/amalgm-mcp/tasks/scheduler.js +105 -0
- package/runtime/scripts/amalgm-mcp/tasks/store.js +139 -0
- package/runtime/scripts/amalgm-mcp/tasks/tools.js +391 -0
- package/runtime/scripts/amalgm-mcp/user-api-keys/rest.js +105 -0
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +389 -0
- package/runtime/scripts/chat-core/adapters/claude.js +163 -0
- package/runtime/scripts/chat-core/adapters/codex.js +313 -0
- package/runtime/scripts/chat-core/adapters/opencode.js +412 -0
- package/runtime/scripts/chat-core/auth.js +177 -0
- package/runtime/scripts/chat-core/contract.js +326 -0
- package/runtime/scripts/chat-core/credentials/store.js +212 -0
- package/runtime/scripts/chat-core/egress.js +87 -0
- package/runtime/scripts/chat-core/engine.js +195 -0
- package/runtime/scripts/chat-core/event-schema.js +231 -0
- package/runtime/scripts/chat-core/events.js +190 -0
- package/runtime/scripts/chat-core/index.js +11 -0
- package/runtime/scripts/chat-core/input.js +50 -0
- package/runtime/scripts/chat-core/normalizers/claude.js +450 -0
- package/runtime/scripts/chat-core/normalizers/codex.js +380 -0
- package/runtime/scripts/chat-core/normalizers/normalizer_spec.md +259 -0
- package/runtime/scripts/chat-core/normalizers/opencode.js +552 -0
- package/runtime/scripts/chat-core/normalizers/tool_contract.md +123 -0
- package/runtime/scripts/chat-core/normalizers/usage_contract.md +304 -0
- package/runtime/scripts/chat-core/parts.js +253 -0
- package/runtime/scripts/chat-core/recorder.js +65 -0
- package/runtime/scripts/chat-core/runtime.js +86 -0
- package/runtime/scripts/chat-core/server.js +163 -0
- package/runtime/scripts/chat-core/sse.js +196 -0
- package/runtime/scripts/chat-core/stores.js +100 -0
- package/runtime/scripts/chat-core/tool-display.js +149 -0
- package/runtime/scripts/chat-core/tool-shape.js +143 -0
- package/runtime/scripts/chat-core/tooling/mcp-bundle.js +161 -0
- package/runtime/scripts/chat-core/tooling/mcp-relay.js +97 -0
- package/runtime/scripts/chat-core/tooling/native-binaries.js +608 -0
- package/runtime/scripts/chat-core/tooling/system-prompt.js +20 -0
- package/runtime/scripts/chat-core/usage.js +343 -0
- package/runtime/scripts/chat-server/config.js +110 -0
- package/runtime/scripts/chat-server/db.js +529 -0
- package/runtime/scripts/chat-server/index.js +33 -0
- package/runtime/scripts/chat-server/model-catalog.js +327 -0
- package/runtime/scripts/chat-server.js +75 -0
- package/runtime/scripts/credential-adapter.js +129 -0
- package/runtime/scripts/fs-watcher.js +888 -0
- package/runtime/scripts/local-gateway.js +852 -0
- package/runtime/scripts/platform-context.txt +246 -0
- package/runtime/scripts/port-monitor.js +175 -0
- package/runtime/scripts/proxy-token-store.js +162 -0
- package/runtime/scripts/runtime-auth.js +163 -0
- package/runtime/scripts/test-claude-code-models.js +87 -0
- package/runtime/tsconfig.json +15 -0
package/README.md
CHANGED
|
@@ -1,7 +1,43 @@
|
|
|
1
1
|
# amalgm
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Install the local Amalgm computer runtime:
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npm i -g amalgm@canary
|
|
7
|
+
amalgm login
|
|
8
|
+
amalgm doctor
|
|
9
|
+
amalgm start
|
|
7
10
|
```
|
|
11
|
+
|
|
12
|
+
`amalgm login` opens a browser approval page, then registers the machine and stores its local tunnel/computer record in `~/.amalgm/computer.json`.
|
|
13
|
+
|
|
14
|
+
If you start from the Amalgm web app or are setting up a remote/headless machine, create a setup code from the web app and run:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
amalgm login --setup-code ABCD-EFGH-JKLM-NPQR
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`amalgm doctor` checks the installed runtime, login record, required local ports, daemon state, and whether an HMAC proxy token is available.
|
|
21
|
+
|
|
22
|
+
`amalgm start` runs the local essentials:
|
|
23
|
+
|
|
24
|
+
- port monitor on `8081`
|
|
25
|
+
- filesystem/auth watcher on `8082`
|
|
26
|
+
- Amalgm MCP on `8083`
|
|
27
|
+
- chat server on `8084`
|
|
28
|
+
- events/previews/artifact tunnel to `wire.events.amalgm.ai`
|
|
29
|
+
- chat tunnel to `amalgm-chat-gateway`
|
|
30
|
+
|
|
31
|
+
The npm supervisor binds local services to `127.0.0.1` by default and connects them outward through the registered Amalgm tunnels. For development without cloud registration, use `amalgm start --local-only`.
|
|
32
|
+
|
|
33
|
+
Useful commands:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
amalgm status
|
|
37
|
+
amalgm logs
|
|
38
|
+
amalgm logs chat-server
|
|
39
|
+
amalgm stop
|
|
40
|
+
amalgm logout
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The runtime in `runtime/` is generated from `amalgm-engine/runtime` during `prepack`, so Electron and the published npm package use the same machine-runtime tree. Local services require an unguessable runtime token for non-health endpoints, and proxy tokens are refreshed through the registered computer credential.
|
package/bin/amalgm.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
require('../lib/cli').main(process.argv).catch((error) => {
|
|
5
|
+
const message = error && error.message ? error.message : String(error);
|
|
6
|
+
console.error(`Error: ${message}`);
|
|
7
|
+
if (process.env.AMALGM_DEBUG && error && error.stack) {
|
|
8
|
+
console.error(error.stack);
|
|
9
|
+
}
|
|
10
|
+
process.exit(1);
|
|
11
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
AUTH_FILE,
|
|
8
|
+
COMPUTER_FILE,
|
|
9
|
+
} = require('./paths');
|
|
10
|
+
|
|
11
|
+
const PROXY_REFRESH_BUFFER_MS = 10 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
const SECRET_COMPUTER_KEYS = new Set([
|
|
14
|
+
'tunnel_token',
|
|
15
|
+
'computer_auth_token',
|
|
16
|
+
'proxy_token',
|
|
17
|
+
'proxy_token_expires_in',
|
|
18
|
+
'proxy_token_expires_at',
|
|
19
|
+
'proxy_token_refreshed_at',
|
|
20
|
+
'proxyToken',
|
|
21
|
+
'proxyTokenExpiresAt',
|
|
22
|
+
'hmac_token',
|
|
23
|
+
'hmacToken',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function ensureDir(dir, mode = 0o700) {
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true, mode });
|
|
28
|
+
try {
|
|
29
|
+
fs.chmodSync(dir, mode);
|
|
30
|
+
} catch {
|
|
31
|
+
// chmod is best-effort on non-POSIX filesystems.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readJson(file, fallback = null) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
38
|
+
} catch {
|
|
39
|
+
return fallback;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeJsonSecret(file, data) {
|
|
44
|
+
ensureDir(path.dirname(file), 0o700);
|
|
45
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
46
|
+
fs.writeFileSync(temp, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
|
|
47
|
+
fs.renameSync(temp, file);
|
|
48
|
+
try {
|
|
49
|
+
fs.chmodSync(file, 0o600);
|
|
50
|
+
} catch {
|
|
51
|
+
// chmod is best-effort on non-POSIX filesystems.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function cleanString(value) {
|
|
56
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hasComputerSecrets(record) {
|
|
60
|
+
if (!record || typeof record !== 'object') return false;
|
|
61
|
+
return Array.from(SECRET_COMPUTER_KEYS).some((key) => cleanString(record[key]));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function publicComputerRecord(record) {
|
|
65
|
+
const out = {
|
|
66
|
+
...(record || {}),
|
|
67
|
+
auth_file: path.basename(AUTH_FILE),
|
|
68
|
+
};
|
|
69
|
+
for (const key of SECRET_COMPUTER_KEYS) delete out[key];
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function authFromRecord(record, existing = null) {
|
|
74
|
+
const now = new Date().toISOString();
|
|
75
|
+
const auth = {
|
|
76
|
+
schema_version: 1,
|
|
77
|
+
...(existing && typeof existing === 'object' ? existing : {}),
|
|
78
|
+
updated_at: now,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
for (const key of ['computer_id', 'device_id', 'user_id', 'app_url']) {
|
|
82
|
+
const value = cleanString(record?.[key]);
|
|
83
|
+
if (value) auth[key] = value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const tunnelToken = cleanString(record?.tunnel_token);
|
|
87
|
+
if (tunnelToken) {
|
|
88
|
+
auth.tunnel = {
|
|
89
|
+
...(auth.tunnel && typeof auth.tunnel === 'object' ? auth.tunnel : {}),
|
|
90
|
+
token: tunnelToken,
|
|
91
|
+
url: cleanString(record?.tunnel_url) || cleanString(auth.tunnel?.url),
|
|
92
|
+
chat_tunnel_url: cleanString(record?.chat_tunnel_url) || cleanString(auth.tunnel?.chat_tunnel_url),
|
|
93
|
+
updated_at: now,
|
|
94
|
+
};
|
|
95
|
+
} else if (record?.tunnel_url || record?.chat_tunnel_url) {
|
|
96
|
+
auth.tunnel = {
|
|
97
|
+
...(auth.tunnel && typeof auth.tunnel === 'object' ? auth.tunnel : {}),
|
|
98
|
+
url: cleanString(record?.tunnel_url) || cleanString(auth.tunnel?.url),
|
|
99
|
+
chat_tunnel_url: cleanString(record?.chat_tunnel_url) || cleanString(auth.tunnel?.chat_tunnel_url),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const computerAuthToken = cleanString(record?.computer_auth_token);
|
|
104
|
+
if (computerAuthToken) {
|
|
105
|
+
auth.machine = {
|
|
106
|
+
...(auth.machine && typeof auth.machine === 'object' ? auth.machine : {}),
|
|
107
|
+
token: computerAuthToken,
|
|
108
|
+
updated_at: now,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const proxyToken = (
|
|
113
|
+
cleanString(record?.proxy_token)
|
|
114
|
+
|| cleanString(record?.proxyToken)
|
|
115
|
+
|| cleanString(record?.hmac_token)
|
|
116
|
+
|| cleanString(record?.hmacToken)
|
|
117
|
+
);
|
|
118
|
+
if (proxyToken) {
|
|
119
|
+
auth.proxy = {
|
|
120
|
+
...(auth.proxy && typeof auth.proxy === 'object' ? auth.proxy : {}),
|
|
121
|
+
token: proxyToken,
|
|
122
|
+
expires_in: Number(record?.proxy_token_expires_in || auth.proxy?.expires_in || 0) || undefined,
|
|
123
|
+
expires_at: cleanString(record?.proxy_token_expires_at)
|
|
124
|
+
|| cleanString(record?.proxyTokenExpiresAt)
|
|
125
|
+
|| cleanString(auth.proxy?.expires_at),
|
|
126
|
+
refreshed_at: cleanString(record?.proxy_token_refreshed_at)
|
|
127
|
+
|| cleanString(auth.proxy?.refreshed_at)
|
|
128
|
+
|| now,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return auth;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function mergeRecordAuth(record, auth) {
|
|
136
|
+
if (!record || typeof record !== 'object') return null;
|
|
137
|
+
const merged = { ...record };
|
|
138
|
+
if (!auth || typeof auth !== 'object') return merged;
|
|
139
|
+
|
|
140
|
+
const authComputerId = cleanString(auth.computer_id);
|
|
141
|
+
const recordComputerId = cleanString(record.computer_id);
|
|
142
|
+
if (authComputerId && recordComputerId && authComputerId !== recordComputerId) return merged;
|
|
143
|
+
|
|
144
|
+
if (!merged.tunnel_token) merged.tunnel_token = cleanString(auth.tunnel?.token) || cleanString(auth.tunnel_token);
|
|
145
|
+
if (!merged.tunnel_url) merged.tunnel_url = cleanString(auth.tunnel?.url);
|
|
146
|
+
if (!merged.chat_tunnel_url) merged.chat_tunnel_url = cleanString(auth.tunnel?.chat_tunnel_url);
|
|
147
|
+
if (!merged.computer_auth_token) {
|
|
148
|
+
merged.computer_auth_token = cleanString(auth.machine?.token) || cleanString(auth.computer_auth_token);
|
|
149
|
+
}
|
|
150
|
+
if (!merged.proxy_token) {
|
|
151
|
+
merged.proxy_token = cleanString(auth.proxy?.token) || cleanString(auth.proxy_token);
|
|
152
|
+
}
|
|
153
|
+
if (!merged.proxy_token_expires_at) {
|
|
154
|
+
merged.proxy_token_expires_at = cleanString(auth.proxy?.expires_at) || cleanString(auth.proxy_token_expires_at);
|
|
155
|
+
}
|
|
156
|
+
if (!merged.proxy_token_expires_in && auth.proxy?.expires_in) {
|
|
157
|
+
merged.proxy_token_expires_in = auth.proxy.expires_in;
|
|
158
|
+
}
|
|
159
|
+
if (!merged.proxy_token_refreshed_at) {
|
|
160
|
+
merged.proxy_token_refreshed_at = cleanString(auth.proxy?.refreshed_at);
|
|
161
|
+
}
|
|
162
|
+
if (!merged.user_id) merged.user_id = cleanString(auth.user_id);
|
|
163
|
+
if (!merged.app_url) merged.app_url = cleanString(auth.app_url);
|
|
164
|
+
return merged;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function loadComputerRecord(options = {}) {
|
|
168
|
+
const record = readJson(COMPUTER_FILE, null);
|
|
169
|
+
if (!record || typeof record !== 'object') return null;
|
|
170
|
+
const auth = readJson(AUTH_FILE, null);
|
|
171
|
+
const merged = mergeRecordAuth(record, auth);
|
|
172
|
+
if (options.migrate && hasComputerSecrets(record)) {
|
|
173
|
+
saveComputerRecord(merged);
|
|
174
|
+
}
|
|
175
|
+
return merged;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function saveComputerRecord(record) {
|
|
179
|
+
const existingAuth = readJson(AUTH_FILE, null);
|
|
180
|
+
const nextAuth = authFromRecord(record, existingAuth);
|
|
181
|
+
const nextRecord = publicComputerRecord(record);
|
|
182
|
+
writeJsonSecret(COMPUTER_FILE, nextRecord);
|
|
183
|
+
writeJsonSecret(AUTH_FILE, nextAuth);
|
|
184
|
+
return mergeRecordAuth(nextRecord, nextAuth);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function deleteComputerAuth() {
|
|
188
|
+
for (const file of [COMPUTER_FILE, AUTH_FILE]) {
|
|
189
|
+
try {
|
|
190
|
+
fs.unlinkSync(file);
|
|
191
|
+
} catch {
|
|
192
|
+
// noop
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function proxyTokenFromRecord(record) {
|
|
198
|
+
return (
|
|
199
|
+
cleanString(process.env.AMALGM_PROXY_TOKEN)
|
|
200
|
+
|| cleanString(record?.proxy_token)
|
|
201
|
+
|| cleanString(record?.proxyToken)
|
|
202
|
+
|| cleanString(record?.hmac_token)
|
|
203
|
+
|| cleanString(record?.hmacToken)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function proxyTokenNeedsRefresh(record) {
|
|
208
|
+
if (!proxyTokenFromRecord(record)) return true;
|
|
209
|
+
const expiresAt = Date.parse(record?.proxy_token_expires_at || record?.proxyTokenExpiresAt || '');
|
|
210
|
+
if (!Number.isFinite(expiresAt)) return false;
|
|
211
|
+
return expiresAt - Date.now() < PROXY_REFRESH_BUFFER_MS;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
AUTH_FILE,
|
|
216
|
+
COMPUTER_FILE,
|
|
217
|
+
deleteComputerAuth,
|
|
218
|
+
hasComputerSecrets,
|
|
219
|
+
loadComputerRecord,
|
|
220
|
+
proxyTokenFromRecord,
|
|
221
|
+
proxyTokenNeedsRefresh,
|
|
222
|
+
saveComputerRecord,
|
|
223
|
+
};
|