amalgm 0.1.245 → 0.1.246
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 +12 -0
- package/lib/cli.js +146 -8
- package/lib/layout.js +89 -4
- package/lib/shared-realtime-tunnel.js +384 -43
- package/lib/supervisor.js +0 -3
- package/package.json +2 -2
- package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
- package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
- package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
- package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
- package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
- package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
- package/runtime/scripts/amalgm-mcp/index.js +4 -0
- package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
- package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
- package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
- package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
- package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
- package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
- package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
- package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
- package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
- package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
- package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
- package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
- package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
- package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
- package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
- package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
- package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
- package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
- package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
- package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
- package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
- package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
- package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
- package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
- package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
- package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
- package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
- package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
- package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
- package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
- package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
- package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
- package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
- package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
- package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
- package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
- package/runtime/scripts/local-gateway.js +1 -0
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
- package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
package/README.md
CHANGED
|
@@ -14,6 +14,18 @@ To move an existing install to the newest stable published build immediately, ru
|
|
|
14
14
|
|
|
15
15
|
`amalgm login` opens a browser approval page, then registers the machine and stores its local tunnel/computer record in `~/.amalgm/computer.json`.
|
|
16
16
|
|
|
17
|
+
After login, register local ground with the cloud-backed entity rail:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
amalgm register path/to/file path/to/folder
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`amalgm register` watches the selected local ground and saves its entity
|
|
24
|
+
records and file contents to the signed-in user's private Amalgm cloud
|
|
25
|
+
resource. `amalgm add` is reserved for the inverse operation—bringing an
|
|
26
|
+
existing cloud entity onto a machine—and is not available until
|
|
27
|
+
materialization ships.
|
|
28
|
+
|
|
17
29
|
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:
|
|
18
30
|
|
|
19
31
|
```sh
|
package/lib/cli.js
CHANGED
|
@@ -114,10 +114,12 @@ function usage() {
|
|
|
114
114
|
'amalgm - local computer runtime',
|
|
115
115
|
'',
|
|
116
116
|
'Usage:',
|
|
117
|
-
' amalgm login [--app-url https://amalgm.ai] [--name "My Mac"] [--setup-code ABCD-EFGH-JKLM-NPQR] [--no-open] [--no-poll]',
|
|
117
|
+
' amalgm login [--app-url https://amalgm.ai] [--name "My Mac"] [--setup-code ABCD-EFGH-JKLM-NPQR] [--no-open] [--no-poll] [--no-start]',
|
|
118
118
|
' amalgm start [--foreground] [--local-only] [--no-auto-update] [--branch main|preview] [--user <user-id>]',
|
|
119
119
|
' amalgm run [--local-only]',
|
|
120
120
|
' amalgm stop',
|
|
121
|
+
' amalgm register [path ...] Register local ground (default: current directory)',
|
|
122
|
+
' amalgm add <entity-uuid> [path] Add a cloud-backed entity to this machine (coming with materialization)',
|
|
121
123
|
' amalgm status',
|
|
122
124
|
' amalgm doctor',
|
|
123
125
|
' amalgm service [install|start|stop|status|uninstall] [--mode auto|systemd|launchd|windows|portable]',
|
|
@@ -466,7 +468,7 @@ function openBrowser(url) {
|
|
|
466
468
|
}
|
|
467
469
|
}
|
|
468
470
|
|
|
469
|
-
async function registerComputer(appUrl, registrationToken, device, name) {
|
|
471
|
+
async function registerComputer(appUrl, registrationToken, device, name, options = {}) {
|
|
470
472
|
const registered = await requestJson(`${appUrl}/api/computers/register`, {
|
|
471
473
|
method: 'POST',
|
|
472
474
|
headers: {
|
|
@@ -498,7 +500,12 @@ async function registerComputer(appUrl, registrationToken, device, name) {
|
|
|
498
500
|
if (!proxyTokenFromRecord(saved)) {
|
|
499
501
|
console.log('HMAC proxy: not minted yet; Amalgm-managed egress will refresh after registration is complete.');
|
|
500
502
|
}
|
|
501
|
-
|
|
503
|
+
if (options['no-start']) {
|
|
504
|
+
console.log('Runtime start skipped by --no-start. Run `amalgm start` to connect this machine.');
|
|
505
|
+
return saved;
|
|
506
|
+
}
|
|
507
|
+
console.log('Starting the canonical user runtime to complete local and cloud setup...');
|
|
508
|
+
await start({ user: saved.user_id });
|
|
502
509
|
return saved;
|
|
503
510
|
}
|
|
504
511
|
|
|
@@ -531,7 +538,7 @@ async function refreshStoredProxyToken(record) {
|
|
|
531
538
|
return saved;
|
|
532
539
|
}
|
|
533
540
|
|
|
534
|
-
async function loginWithSetupCode(appUrl, setupCode, device, name) {
|
|
541
|
+
async function loginWithSetupCode(appUrl, setupCode, device, name, options = {}) {
|
|
535
542
|
const normalizedCode = normalizeSetupCode(setupCode);
|
|
536
543
|
if (normalizedCode.length !== SETUP_CODE_LENGTH) {
|
|
537
544
|
throw new Error('Invalid setup code. It should look like ABCD-EFGH-JKLM-NPQR.');
|
|
@@ -555,7 +562,7 @@ async function loginWithSetupCode(appUrl, setupCode, device, name) {
|
|
|
555
562
|
throw new Error('Setup code was accepted, but no registration token was returned.');
|
|
556
563
|
}
|
|
557
564
|
|
|
558
|
-
return registerComputer(appUrl, redeemed.computer_registration_token, device, name);
|
|
565
|
+
return registerComputer(appUrl, redeemed.computer_registration_token, device, name, options);
|
|
559
566
|
}
|
|
560
567
|
|
|
561
568
|
function appUrlFlag(appUrl) {
|
|
@@ -665,7 +672,7 @@ async function startCliLogin(appUrl, device, name, options) {
|
|
|
665
672
|
console.log(`Approved by: ${polled.approved_by.label}`);
|
|
666
673
|
}
|
|
667
674
|
console.log('Browser approved. Registering this computer...');
|
|
668
|
-
return registerComputer(appUrl, polled.computer_registration_token, device, name);
|
|
675
|
+
return registerComputer(appUrl, polled.computer_registration_token, device, name, options);
|
|
669
676
|
}
|
|
670
677
|
|
|
671
678
|
if (polled.status === 'registered') {
|
|
@@ -692,7 +699,7 @@ async function login(options) {
|
|
|
692
699
|
const setupCode = options['setup-code'] || options.code;
|
|
693
700
|
|
|
694
701
|
if (setupCode) {
|
|
695
|
-
return loginWithSetupCode(appUrl, setupCode, device, name);
|
|
702
|
+
return loginWithSetupCode(appUrl, setupCode, device, name, options);
|
|
696
703
|
}
|
|
697
704
|
|
|
698
705
|
return startCliLogin(appUrl, device, name, options);
|
|
@@ -835,6 +842,45 @@ function relaunchCurrentCommandAfterLayoutChange() {
|
|
|
835
842
|
return Number.isInteger(result.status) ? result.status : 0;
|
|
836
843
|
}
|
|
837
844
|
|
|
845
|
+
/** A login process begins before it knows the account scope. The registered
|
|
846
|
+
* runtime must not inherit that provisional `local` address or a caller's
|
|
847
|
+
* explicit AMALGM_DIR; launch the canonical process with the stable user id
|
|
848
|
+
* and let paths.js derive its user folder afresh. */
|
|
849
|
+
function canonicalStartLaunch(userId, options = {}, inheritedEnv = process.env) {
|
|
850
|
+
const userScope = sanitizeScopeSegment(userId, '');
|
|
851
|
+
if (!userScope) throw new Error('cannot start the canonical runtime without a user id');
|
|
852
|
+
const env = {
|
|
853
|
+
...inheritedEnv,
|
|
854
|
+
AMALGM_RUNTIME_USER_ID: userScope,
|
|
855
|
+
AMALGM_USER_SCOPE: userScope,
|
|
856
|
+
AMALGM_LAYOUT_RELAUNCHED: '1',
|
|
857
|
+
};
|
|
858
|
+
delete env.AMALGM_DIR;
|
|
859
|
+
delete env.AMALGM_RUNTIME_STATE_DIR;
|
|
860
|
+
delete env.AMALGM_RUNTIME_INSTANCE_ID;
|
|
861
|
+
const args = [path.join(__dirname, '..', 'bin', 'amalgm.js'), 'start', '--user', userScope];
|
|
862
|
+
if (options.foreground) args.push('--foreground');
|
|
863
|
+
if (options['local-only']) args.push('--local-only');
|
|
864
|
+
if (options['no-auto-update']) args.push('--no-auto-update');
|
|
865
|
+
if (options['no-service']) args.push('--no-service');
|
|
866
|
+
if (options.mode) args.push('--mode', String(options.mode));
|
|
867
|
+
return { args, env };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function relaunchStartForUser(userId, options = {}) {
|
|
871
|
+
const launch = canonicalStartLaunch(userId, options);
|
|
872
|
+
const result = spawnSync(process.execPath, launch.args, {
|
|
873
|
+
stdio: 'inherit',
|
|
874
|
+
env: launch.env,
|
|
875
|
+
});
|
|
876
|
+
if (result.error) throw result.error;
|
|
877
|
+
if (result.signal) {
|
|
878
|
+
process.kill(process.pid, result.signal);
|
|
879
|
+
return 1;
|
|
880
|
+
}
|
|
881
|
+
return Number.isInteger(result.status) ? result.status : 0;
|
|
882
|
+
}
|
|
883
|
+
|
|
838
884
|
function shouldRelaunchForAdoptedDir(adoption) {
|
|
839
885
|
return Boolean(
|
|
840
886
|
adoption?.dir
|
|
@@ -845,6 +891,13 @@ function shouldRelaunchForAdoptedDir(adoption) {
|
|
|
845
891
|
}
|
|
846
892
|
|
|
847
893
|
async function start(options) {
|
|
894
|
+
const requestedUser = typeof options.user === 'string'
|
|
895
|
+
? sanitizeScopeSegment(options.user, '')
|
|
896
|
+
: '';
|
|
897
|
+
if (requestedUser && requestedUser !== AMALGM_RUNTIME_USER_SCOPE) {
|
|
898
|
+
process.exitCode = relaunchStartForUser(requestedUser, options);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
848
901
|
ensureBaseDirs();
|
|
849
902
|
|
|
850
903
|
if (await updateBeforeStartIfRequested(options)) {
|
|
@@ -1391,6 +1444,87 @@ async function logout() {
|
|
|
1391
1444
|
console.log('Logged out locally. The device id remains so re-login updates the same computer.');
|
|
1392
1445
|
}
|
|
1393
1446
|
|
|
1447
|
+
/**
|
|
1448
|
+
* `amalgm register [path ...]` — register selected local ground with the running runtime.
|
|
1449
|
+
*
|
|
1450
|
+
* The CLI is a human interface over the runtime's operation, never a
|
|
1451
|
+
* second implementation (docs/realtime-rails.md §3): it resolves the
|
|
1452
|
+
* path, authenticates, and prints what came back. Every rule about what
|
|
1453
|
+
* may be registered lives behind the endpoint.
|
|
1454
|
+
*/
|
|
1455
|
+
async function register(positionals) {
|
|
1456
|
+
const targets = (positionals.length > 0 ? positionals : [process.cwd()])
|
|
1457
|
+
.map((target) => path.resolve(target));
|
|
1458
|
+
|
|
1459
|
+
// A resolved port is not a running runtime — the manifest supplies a
|
|
1460
|
+
// default whether or not anything listens — so both "no port for this
|
|
1461
|
+
// scope" and "nothing answered" mean the same thing to the user.
|
|
1462
|
+
const port = servicePorts().find(([name]) => name === 'amalgm-mcp')?.[1];
|
|
1463
|
+
let answer = null;
|
|
1464
|
+
if (port) {
|
|
1465
|
+
try {
|
|
1466
|
+
answer = await runtimePost(port, '/entities/register', { paths: targets });
|
|
1467
|
+
} catch (error) {
|
|
1468
|
+
if (error.code !== 'ECONNREFUSED') throw error;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
if (!answer) {
|
|
1472
|
+
console.error('Amalgm runtime is not running. Start it with `amalgm start`.');
|
|
1473
|
+
process.exitCode = 1;
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
if (answer.status !== 200) {
|
|
1478
|
+
console.error(`Error: ${answer.payload?.error || `runtime responded ${answer.status}`}`);
|
|
1479
|
+
process.exitCode = 1;
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const results = Array.isArray(answer.payload?.results)
|
|
1483
|
+
? answer.payload.results
|
|
1484
|
+
: [{ rootPath: answer.payload?.rootPath, record: answer.payload?.record }];
|
|
1485
|
+
for (let index = 0; index < results.length; index += 1) {
|
|
1486
|
+
const { rootPath, record } = results[index];
|
|
1487
|
+
const target = targets[index];
|
|
1488
|
+
console.log(`Registered ${rootPath}`);
|
|
1489
|
+
console.log(` ${record.type} ${record.uuid}`);
|
|
1490
|
+
if (rootPath !== target) console.log(` (${target} is already covered by this tree)`);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
/** POST JSON to a local runtime service, carrying the runtime token. */
|
|
1495
|
+
function runtimePost(port, requestPath, body) {
|
|
1496
|
+
const payload = JSON.stringify(body);
|
|
1497
|
+
const token = readJson(RUNTIME_TOKEN_FILE, null)?.token || '';
|
|
1498
|
+
return new Promise((resolve, reject) => {
|
|
1499
|
+
const req = http.request({
|
|
1500
|
+
host: '127.0.0.1',
|
|
1501
|
+
port,
|
|
1502
|
+
path: requestPath,
|
|
1503
|
+
method: 'POST',
|
|
1504
|
+
headers: {
|
|
1505
|
+
'content-type': 'application/json',
|
|
1506
|
+
'content-length': Buffer.byteLength(payload),
|
|
1507
|
+
...(token ? { 'x-amalgm-runtime-token': token } : {}),
|
|
1508
|
+
},
|
|
1509
|
+
}, (res) => {
|
|
1510
|
+
let text = '';
|
|
1511
|
+
res.setEncoding('utf8');
|
|
1512
|
+
res.on('data', (chunk) => { text += chunk; });
|
|
1513
|
+
res.on('end', () => {
|
|
1514
|
+
let parsed = null;
|
|
1515
|
+
try {
|
|
1516
|
+
parsed = JSON.parse(text);
|
|
1517
|
+
} catch {
|
|
1518
|
+
parsed = null;
|
|
1519
|
+
}
|
|
1520
|
+
resolve({ status: res.statusCode, payload: parsed });
|
|
1521
|
+
});
|
|
1522
|
+
});
|
|
1523
|
+
req.on('error', reject);
|
|
1524
|
+
req.end(payload);
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1394
1528
|
async function main(argv) {
|
|
1395
1529
|
const { command, options, positionals } = parseArgs(argv);
|
|
1396
1530
|
|
|
@@ -1413,6 +1547,10 @@ async function main(argv) {
|
|
|
1413
1547
|
});
|
|
1414
1548
|
}
|
|
1415
1549
|
if (command === 'stop') return stop();
|
|
1550
|
+
if (command === 'register') return register(positionals);
|
|
1551
|
+
if (command === 'add') {
|
|
1552
|
+
throw new Error('`amalgm add` is reserved for cloud-to-local materialization, which is not available yet. Use `amalgm register <path>` for local ground.');
|
|
1553
|
+
}
|
|
1416
1554
|
if (command === 'status') return status();
|
|
1417
1555
|
if (command === 'doctor') return doctor();
|
|
1418
1556
|
if (command === 'service') return serviceCommand(options, positionals);
|
|
@@ -1426,4 +1564,4 @@ async function main(argv) {
|
|
|
1426
1564
|
process.exitCode = 1;
|
|
1427
1565
|
}
|
|
1428
1566
|
|
|
1429
|
-
module.exports = { main };
|
|
1567
|
+
module.exports = { canonicalStartLaunch, main };
|
package/lib/layout.js
CHANGED
|
@@ -32,15 +32,40 @@ const path = require('path');
|
|
|
32
32
|
const ROOT_MANIFEST_NAME = '.amalgm-root.json';
|
|
33
33
|
const USER_MANIFEST_NAME = '.amalgm-user.json';
|
|
34
34
|
const LAYOUT_VERSION = 1;
|
|
35
|
+
const USER_HOME_MANIFEST_NAME = 'home.json';
|
|
36
|
+
const USER_HOME_SCHEMA_VERSION = 1;
|
|
35
37
|
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
+
// Stable per-user product namespaces. Their authority is NOT inferred from
|
|
39
|
+
// their path: user content and exact platform-managed targets are declared
|
|
40
|
+
// separately below (docs/user-cloud-bootstrap.md).
|
|
38
41
|
const PRIMITIVE_DIRS = Object.freeze([
|
|
39
42
|
'agents',
|
|
40
43
|
'apps',
|
|
41
44
|
'automations',
|
|
42
45
|
'toolbox',
|
|
43
46
|
'workspaces',
|
|
47
|
+
'system',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const USER_HOME_ROOTS = Object.freeze([
|
|
51
|
+
{ key: 'agents', relativePath: 'agents' },
|
|
52
|
+
{ key: 'apps', relativePath: 'apps' },
|
|
53
|
+
{ key: 'automations', relativePath: 'automations' },
|
|
54
|
+
{ key: 'toolbox', relativePath: 'toolbox' },
|
|
55
|
+
{ key: 'workspaces', relativePath: 'workspaces' },
|
|
56
|
+
{ key: 'system', relativePath: 'system' },
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
// Platform management is an exact-node fact. It deliberately does not
|
|
60
|
+
// cascade from `system/`: human permissions may still inherit normally, while
|
|
61
|
+
// only these particular entities are product-managed.
|
|
62
|
+
const USER_HOME_EXACT_MANAGED = Object.freeze([
|
|
63
|
+
...USER_HOME_ROOTS.map(({ key, relativePath }) => ({
|
|
64
|
+
key: `${key}-root`, relativePath, rootKey: key,
|
|
65
|
+
})),
|
|
66
|
+
{ key: 'home-manifest', relativePath: 'system/home.json', rootKey: 'system' },
|
|
67
|
+
{ key: 'defaults', relativePath: 'system/defaults', rootKey: 'system' },
|
|
68
|
+
{ key: 'policy', relativePath: 'system/policy', rootKey: 'system' },
|
|
44
69
|
]);
|
|
45
70
|
|
|
46
71
|
function readJson(file, fallback = null) {
|
|
@@ -56,6 +81,51 @@ function writeJson(file, data) {
|
|
|
56
81
|
fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
|
|
57
82
|
}
|
|
58
83
|
|
|
84
|
+
function declaredUserHome(userDir) {
|
|
85
|
+
const root = path.resolve(userDir);
|
|
86
|
+
return {
|
|
87
|
+
schemaVersion: USER_HOME_SCHEMA_VERSION,
|
|
88
|
+
userDir: root,
|
|
89
|
+
roots: USER_HOME_ROOTS.map((entry) => ({
|
|
90
|
+
...entry,
|
|
91
|
+
path: path.join(root, entry.relativePath),
|
|
92
|
+
})),
|
|
93
|
+
exactManaged: USER_HOME_EXACT_MANAGED.map((entry) => ({
|
|
94
|
+
...entry,
|
|
95
|
+
path: path.join(root, entry.relativePath),
|
|
96
|
+
scope: 'exact',
|
|
97
|
+
})),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Materialize the fixed, safe bootstrap surface. This does not register or
|
|
103
|
+
* synchronize anything; runtime bootstrap calls the shared `amalgm register`
|
|
104
|
+
* boundary for that. Keeping those acts separate prevents login from growing
|
|
105
|
+
* a second identity registrar or silently enrolling machine-local state.
|
|
106
|
+
*/
|
|
107
|
+
function ensureUserHome(userDir) {
|
|
108
|
+
const declaration = declaredUserHome(userDir);
|
|
109
|
+
for (const root of declaration.roots) {
|
|
110
|
+
fs.mkdirSync(root.path, { recursive: true, mode: 0o700 });
|
|
111
|
+
}
|
|
112
|
+
for (const managed of declaration.exactManaged) {
|
|
113
|
+
if (path.extname(managed.path)) continue;
|
|
114
|
+
fs.mkdirSync(managed.path, { recursive: true, mode: 0o700 });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const manifest = declaration.exactManaged.find((entry) => entry.key === 'home-manifest');
|
|
118
|
+
if (manifest && !fs.existsSync(manifest.path)) {
|
|
119
|
+
writeJson(manifest.path, {
|
|
120
|
+
schemaVersion: USER_HOME_SCHEMA_VERSION,
|
|
121
|
+
kind: 'amalgm-user-home',
|
|
122
|
+
roots: declaration.roots.map(({ key, relativePath }) => ({ key, path: relativePath })),
|
|
123
|
+
exactManaged: declaration.exactManaged.map(({ key, relativePath, scope }) => ({ key, path: relativePath, scope })),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return declaration;
|
|
127
|
+
}
|
|
128
|
+
|
|
59
129
|
function lstatOrNull(target) {
|
|
60
130
|
try {
|
|
61
131
|
return fs.lstatSync(target);
|
|
@@ -249,17 +319,25 @@ function ensureUserDir(homeDir, { userId, email }) {
|
|
|
249
319
|
const existing = findUserDirByUserId(homeDir, cleanId);
|
|
250
320
|
if (existing) {
|
|
251
321
|
if (cleanEmail && readUserManifest(existing)?.canonicalEmail !== cleanEmail) {
|
|
252
|
-
|
|
322
|
+
const adopted = adoptUserEmail(homeDir, { userId: cleanId, email: cleanEmail }).dir;
|
|
323
|
+
ensureUserHome(adopted);
|
|
324
|
+
return adopted;
|
|
253
325
|
}
|
|
254
326
|
indexUserFolder(homeDir, path.basename(existing), { userId: cleanId, email: cleanEmail });
|
|
327
|
+
ensureUserHome(existing);
|
|
255
328
|
return existing;
|
|
256
329
|
}
|
|
257
330
|
|
|
258
331
|
const legacyDir = path.join(homeDir, 'users', sanitizeFolderSegment(cleanId));
|
|
259
332
|
if (lstatOrNull(legacyDir)?.isDirectory()) {
|
|
260
333
|
writeUserManifest(legacyDir, { userId: cleanId, email: cleanEmail });
|
|
261
|
-
if (cleanEmail)
|
|
334
|
+
if (cleanEmail) {
|
|
335
|
+
const adopted = adoptUserEmail(homeDir, { userId: cleanId, email: cleanEmail }).dir;
|
|
336
|
+
ensureUserHome(adopted);
|
|
337
|
+
return adopted;
|
|
338
|
+
}
|
|
262
339
|
indexUserFolder(homeDir, path.basename(legacyDir), { userId: cleanId, email: '' });
|
|
340
|
+
ensureUserHome(legacyDir);
|
|
263
341
|
return legacyDir;
|
|
264
342
|
}
|
|
265
343
|
|
|
@@ -268,6 +346,7 @@ function ensureUserDir(homeDir, { userId, email }) {
|
|
|
268
346
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
269
347
|
writeUserManifest(dir, { userId: cleanId, email: cleanEmail });
|
|
270
348
|
indexUserFolder(homeDir, folderName, { userId: cleanId, email: cleanEmail });
|
|
349
|
+
ensureUserHome(dir);
|
|
271
350
|
return dir;
|
|
272
351
|
}
|
|
273
352
|
|
|
@@ -323,8 +402,14 @@ module.exports = {
|
|
|
323
402
|
PRIMITIVE_DIRS,
|
|
324
403
|
ROOT_MANIFEST_NAME,
|
|
325
404
|
USER_MANIFEST_NAME,
|
|
405
|
+
USER_HOME_EXACT_MANAGED,
|
|
406
|
+
USER_HOME_MANIFEST_NAME,
|
|
407
|
+
USER_HOME_ROOTS,
|
|
408
|
+
USER_HOME_SCHEMA_VERSION,
|
|
326
409
|
adoptUserEmail,
|
|
410
|
+
declaredUserHome,
|
|
327
411
|
emailFolderName,
|
|
412
|
+
ensureUserHome,
|
|
328
413
|
ensureRootManifest,
|
|
329
414
|
ensureUserDir,
|
|
330
415
|
createCompatLink,
|