@amalgm/apps 0.1.0

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.
Files changed (98) hide show
  1. package/PURPOSE.md +98 -0
  2. package/README.md +61 -0
  3. package/dist/apps.d.ts +58 -0
  4. package/dist/apps.js +274 -0
  5. package/dist/apps.js.map +1 -0
  6. package/dist/bin/apps.d.ts +2 -0
  7. package/dist/bin/apps.js +6 -0
  8. package/dist/bin/apps.js.map +1 -0
  9. package/dist/bin/mcp.d.ts +2 -0
  10. package/dist/bin/mcp.js +15 -0
  11. package/dist/bin/mcp.js.map +1 -0
  12. package/dist/boundaries.d.ts +11 -0
  13. package/dist/boundaries.js +55 -0
  14. package/dist/boundaries.js.map +1 -0
  15. package/dist/changes.d.ts +14 -0
  16. package/dist/changes.js +23 -0
  17. package/dist/changes.js.map +1 -0
  18. package/dist/cli.d.ts +24 -0
  19. package/dist/cli.js +138 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/command.d.ts +3 -0
  22. package/dist/command.js +40 -0
  23. package/dist/command.js.map +1 -0
  24. package/dist/faults.d.ts +21 -0
  25. package/dist/faults.js +59 -0
  26. package/dist/faults.js.map +1 -0
  27. package/dist/generations.d.ts +28 -0
  28. package/dist/generations.js +83 -0
  29. package/dist/generations.js.map +1 -0
  30. package/dist/health.d.ts +35 -0
  31. package/dist/health.js +116 -0
  32. package/dist/health.js.map +1 -0
  33. package/dist/identity.d.ts +10 -0
  34. package/dist/identity.js +59 -0
  35. package/dist/identity.js.map +1 -0
  36. package/dist/index.d.ts +8 -0
  37. package/dist/index.js +8 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/lease.d.ts +30 -0
  40. package/dist/lease.js +82 -0
  41. package/dist/lease.js.map +1 -0
  42. package/dist/logs.d.ts +11 -0
  43. package/dist/logs.js +40 -0
  44. package/dist/logs.js.map +1 -0
  45. package/dist/manifest.d.ts +6 -0
  46. package/dist/manifest.js +105 -0
  47. package/dist/manifest.js.map +1 -0
  48. package/dist/mcp-server.d.ts +3 -0
  49. package/dist/mcp-server.js +65 -0
  50. package/dist/mcp-server.js.map +1 -0
  51. package/dist/mcp.d.ts +4 -0
  52. package/dist/mcp.js +171 -0
  53. package/dist/mcp.js.map +1 -0
  54. package/dist/operations.d.ts +10 -0
  55. package/dist/operations.js +31 -0
  56. package/dist/operations.js.map +1 -0
  57. package/dist/ownership.d.ts +11 -0
  58. package/dist/ownership.js +75 -0
  59. package/dist/ownership.js.map +1 -0
  60. package/dist/package.d.ts +20 -0
  61. package/dist/package.js +204 -0
  62. package/dist/package.js.map +1 -0
  63. package/dist/presentation.d.ts +9 -0
  64. package/dist/presentation.js +63 -0
  65. package/dist/presentation.js.map +1 -0
  66. package/dist/process.d.ts +10 -0
  67. package/dist/process.js +68 -0
  68. package/dist/process.js.map +1 -0
  69. package/dist/reconciler.d.ts +27 -0
  70. package/dist/reconciler.js +103 -0
  71. package/dist/reconciler.js.map +1 -0
  72. package/dist/releases.d.ts +26 -0
  73. package/dist/releases.js +108 -0
  74. package/dist/releases.js.map +1 -0
  75. package/dist/restarts.d.ts +40 -0
  76. package/dist/restarts.js +137 -0
  77. package/dist/restarts.js.map +1 -0
  78. package/dist/runtime.d.ts +19 -0
  79. package/dist/runtime.js +57 -0
  80. package/dist/runtime.js.map +1 -0
  81. package/dist/schema.d.ts +3 -0
  82. package/dist/schema.js +80 -0
  83. package/dist/schema.js.map +1 -0
  84. package/dist/source-import.d.ts +4 -0
  85. package/dist/source-import.js +77 -0
  86. package/dist/source-import.js.map +1 -0
  87. package/dist/store.d.ts +29 -0
  88. package/dist/store.js +221 -0
  89. package/dist/store.js.map +1 -0
  90. package/dist/supervisor.d.ts +29 -0
  91. package/dist/supervisor.js +279 -0
  92. package/dist/supervisor.js.map +1 -0
  93. package/dist/types.d.ts +264 -0
  94. package/dist/types.js +2 -0
  95. package/dist/types.js.map +1 -0
  96. package/docs/ENGINE_INTEGRATION.md +68 -0
  97. package/docs/SUPERVISION.md +73 -0
  98. package/package.json +59 -0
@@ -0,0 +1,59 @@
1
+ import crypto from 'node:crypto';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { resolveProductStateDir } from '@amalgm/core/identity';
5
+ import { APP_REF_PATTERN } from '@amalgm/core/transport';
6
+ const REF_ALPHABET = 'abcdefghijklmnopqrstuvwxyz0123456789';
7
+ function defaultStateDir(env = process.env, scope) {
8
+ return resolveProductStateDir({
9
+ product: 'apps',
10
+ primitive: 'apps',
11
+ env,
12
+ homedir: path.join(os.homedir(), '.amalgm'),
13
+ scope,
14
+ });
15
+ }
16
+ function allocatePort(store, preferred, minimum, maximum) {
17
+ const apps = store.listApps();
18
+ if (preferred !== undefined) {
19
+ const port = Number(preferred);
20
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
21
+ throw new Error('port must be between 1 and 65535');
22
+ }
23
+ if (apps.some((app) => app.port === port))
24
+ throw new Error(`Port is already registered: ${port}`);
25
+ return port;
26
+ }
27
+ const used = new Set(apps.map((app) => app.port));
28
+ for (let port = minimum; port <= maximum; port += 1) {
29
+ if (!used.has(port))
30
+ return port;
31
+ }
32
+ throw new Error('No app ports are available');
33
+ }
34
+ function allocateRef(store) {
35
+ const used = new Set(store.listApps().map((app) => app.appRef));
36
+ for (let attempt = 0; attempt < 20; attempt += 1) {
37
+ let candidate = '';
38
+ while (candidate.length < 12) {
39
+ for (const byte of crypto.randomBytes(16)) {
40
+ if (byte >= 252)
41
+ continue;
42
+ candidate += REF_ALPHABET[byte % REF_ALPHABET.length];
43
+ if (candidate.length === 12)
44
+ break;
45
+ }
46
+ }
47
+ if (!used.has(candidate))
48
+ return candidate;
49
+ }
50
+ throw new Error('Could not allocate an app ref');
51
+ }
52
+ function assertAppRef(value) {
53
+ if (!APP_REF_PATTERN.test(String(value || ''))) {
54
+ throw new Error('app ref must contain 8-24 lowercase letters or digits');
55
+ }
56
+ return String(value);
57
+ }
58
+ export { APP_REF_PATTERN, allocatePort, allocateRef, assertAppRef, defaultStateDir, };
59
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { Apps, defaultStateDir } from './apps.js';
2
+ export { runCli } from './cli.js';
3
+ export * from './lease.js';
4
+ export { normalizeManifest, readManifest } from './manifest.js';
5
+ export { createMcpTools } from './mcp.js';
6
+ export { createMcpServer } from './mcp-server.js';
7
+ export { inspectPackage, materialize, packPackage, unpackPackage, validatePackageFiles, } from './package.js';
8
+ export type * from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { Apps, defaultStateDir } from './apps.js';
2
+ export { runCli } from './cli.js';
3
+ export * from './lease.js';
4
+ export { normalizeManifest, readManifest } from './manifest.js';
5
+ export { createMcpTools } from './mcp.js';
6
+ export { createMcpServer } from './mcp-server.js';
7
+ export { inspectPackage, materialize, packPackage, unpackPackage, validatePackageFiles, } from './package.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,cAAc,EACd,WAAW,EACX,WAAW,EACX,aAAa,EACb,oBAAoB,GACrB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,30 @@
1
+ declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 30000;
2
+ interface AppGrants {
3
+ appId: string | null;
4
+ appRef: string | null;
5
+ publicUrl: string | null;
6
+ dataDir: string | null;
7
+ port: number | null;
8
+ runtimeUrl: string | null;
9
+ chatServerUrl: string | null;
10
+ }
11
+ interface LeaseResult extends Record<string, unknown> {
12
+ ok: boolean;
13
+ error?: string;
14
+ }
15
+ interface ConnectOptions {
16
+ heartbeatIntervalMs?: number;
17
+ onError?: (error: Error) => unknown;
18
+ }
19
+ interface AppLease {
20
+ active: boolean;
21
+ stop(): void;
22
+ }
23
+ type AppEnvironment = Record<string, string | undefined>;
24
+ declare function grants(env?: AppEnvironment): AppGrants;
25
+ declare function isManaged(env?: AppEnvironment): boolean;
26
+ declare function ready(options?: Pick<ConnectOptions, 'heartbeatIntervalMs'>, env?: AppEnvironment): Promise<LeaseResult>;
27
+ declare function heartbeat(env?: AppEnvironment): Promise<LeaseResult>;
28
+ declare function connect(options?: ConnectOptions, env?: AppEnvironment): AppLease;
29
+ export { DEFAULT_HEARTBEAT_INTERVAL_MS, connect, grants, heartbeat, isManaged, ready, };
30
+ export type { AppEnvironment, AppGrants, AppLease, ConnectOptions, LeaseResult };
package/dist/lease.js ADDED
@@ -0,0 +1,82 @@
1
+ import { runtimeTokenFromEnv, withRuntimeToken } from '@amalgm/core/transport';
2
+ const REQUEST_TIMEOUT_MS = 3_000;
3
+ const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
4
+ function grants(env = process.env) {
5
+ return {
6
+ appId: env.AMALGM_APP_ID || null,
7
+ appRef: env.AMALGM_APP_REF || null,
8
+ publicUrl: env.AMALGM_APP_URL || null,
9
+ dataDir: env.AMALGM_APP_DATA_DIR || null,
10
+ port: env.PORT ? Number(env.PORT) : null,
11
+ runtimeUrl: env.AMALGM_MCP_URL || null,
12
+ chatServerUrl: env.AMALGM_CHAT_SERVER_URL || null,
13
+ };
14
+ }
15
+ function isManaged(env = process.env) {
16
+ const issued = grants(env);
17
+ return Boolean(issued.appId && issued.runtimeUrl);
18
+ }
19
+ async function post(pathname, body, env = process.env) {
20
+ const issued = grants(env);
21
+ if (!issued.appId || !issued.runtimeUrl) {
22
+ return { ok: false, error: 'not managed by an Amalgm runtime' };
23
+ }
24
+ const headers = withRuntimeToken({ 'content-type': 'application/json' }, runtimeTokenFromEnv(env));
25
+ try {
26
+ const response = await fetch(new URL(pathname, issued.runtimeUrl), {
27
+ method: 'POST',
28
+ headers,
29
+ body: JSON.stringify({ app_id: issued.appId, ...body }),
30
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
31
+ });
32
+ const payload = await response.json().catch(() => ({}));
33
+ if (!response.ok)
34
+ return { ok: false, error: String(payload.error || `runtime answered ${response.status}`) };
35
+ return { ...payload, ok: payload.ok !== false };
36
+ }
37
+ catch (error) {
38
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
39
+ }
40
+ }
41
+ function ready(options = {}, env = process.env) {
42
+ return post('/apps/ready', {
43
+ ...(options.heartbeatIntervalMs
44
+ ? { heartbeat_interval_ms: options.heartbeatIntervalMs }
45
+ : {}),
46
+ }, env);
47
+ }
48
+ function heartbeat(env = process.env) {
49
+ return post('/apps/heartbeat', {}, env);
50
+ }
51
+ function connect(options = {}, env = process.env) {
52
+ if (!isManaged(env))
53
+ return { active: false, stop() { } };
54
+ const intervalMs = Number(options.heartbeatIntervalMs) || DEFAULT_HEARTBEAT_INTERVAL_MS;
55
+ const onError = typeof options.onError === 'function' ? options.onError : null;
56
+ let failing = false;
57
+ const report = (result) => {
58
+ if (result.ok) {
59
+ failing = false;
60
+ return;
61
+ }
62
+ if (failing)
63
+ return;
64
+ failing = true;
65
+ const error = new Error(result.error || 'health announcement failed');
66
+ if (onError)
67
+ onError(error);
68
+ else
69
+ console.warn(`[amalgm/apps] ${error.message}`);
70
+ };
71
+ ready({ heartbeatIntervalMs: intervalMs }, env).then(report);
72
+ const timer = setInterval(() => heartbeat(env).then(report), intervalMs);
73
+ timer.unref?.();
74
+ return {
75
+ active: true,
76
+ stop() {
77
+ clearInterval(timer);
78
+ },
79
+ };
80
+ }
81
+ export { DEFAULT_HEARTBEAT_INTERVAL_MS, connect, grants, heartbeat, isManaged, ready, };
82
+ //# sourceMappingURL=lease.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lease.js","sourceRoot":"","sources":["../src/lease.ts"],"names":[],"mappings":"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"}
package/dist/logs.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Writable } from 'node:stream';
2
+ declare const DEFAULT_MAX_LOG_BYTES: number;
3
+ declare class CappedLog extends Writable {
4
+ private remaining;
5
+ private readonly output;
6
+ constructor(file: string, maxBytes?: number);
7
+ _write(chunk: Buffer, encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
8
+ _final(callback: (error?: Error | null) => void): void;
9
+ _destroy(error: Error | null, callback: (error?: Error | null) => void): void;
10
+ }
11
+ export { CappedLog, DEFAULT_MAX_LOG_BYTES };
package/dist/logs.js ADDED
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs';
2
+ import { Writable } from 'node:stream';
3
+ const DEFAULT_MAX_LOG_BYTES = 10 * 1024 * 1024;
4
+ function rotate(file, maxBytes) {
5
+ const size = fs.existsSync(file) ? fs.statSync(file).size : 0;
6
+ if (size < maxBytes)
7
+ return size;
8
+ const previous = `${file}.1`;
9
+ fs.rmSync(previous, { force: true });
10
+ fs.renameSync(file, previous);
11
+ return 0;
12
+ }
13
+ class CappedLog extends Writable {
14
+ remaining;
15
+ output;
16
+ constructor(file, maxBytes = DEFAULT_MAX_LOG_BYTES) {
17
+ super();
18
+ this.remaining = maxBytes - rotate(file, maxBytes);
19
+ this.output = fs.createWriteStream(file, { flags: 'a' });
20
+ this.output.on('error', (error) => this.destroy(error));
21
+ }
22
+ _write(chunk, encoding, callback) {
23
+ if (this.remaining <= 0) {
24
+ callback();
25
+ return;
26
+ }
27
+ const content = chunk.length > this.remaining ? chunk.subarray(0, this.remaining) : chunk;
28
+ this.remaining -= content.length;
29
+ this.output.write(content, encoding, callback);
30
+ }
31
+ _final(callback) {
32
+ this.output.end(callback);
33
+ }
34
+ _destroy(error, callback) {
35
+ this.output.destroy();
36
+ callback(error);
37
+ }
38
+ }
39
+ export { CappedLog, DEFAULT_MAX_LOG_BYTES };
40
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,MAAM,qBAAqB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,SAAS,MAAM,CAAC,IAAY,EAAE,QAAgB;IAC5C,MAAM,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,IAAI,IAAI,GAAG,QAAQ;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI,CAAC;IAC7B,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,SAAU,SAAQ,QAAQ;IACtB,SAAS,CAAS;IACT,MAAM,CAAc;IAErC,YAAY,IAAY,EAAE,QAAQ,GAAG,qBAAqB;QACxD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,iBAAiB,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CACJ,KAAa,EACb,QAAwB,EACxB,QAAwC;QAExC,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;YACxB,QAAQ,EAAE,CAAC;YACX,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC1F,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,CAAC,QAAwC;QAC7C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,QAAQ,CAAC,KAAmB,EAAE,QAAwC;QACpE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACtB,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC;CACF;AAED,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { AppManifest } from './types.js';
2
+ declare const MANIFEST_FILE = "amalgm.app.json";
3
+ declare const SCHEMA_VERSION = 1;
4
+ declare function normalizeManifest(value: unknown): AppManifest;
5
+ declare function readManifest(rootDir: string): AppManifest;
6
+ export { MANIFEST_FILE, SCHEMA_VERSION, normalizeManifest, readManifest, };
@@ -0,0 +1,105 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { errorMessage } from './boundaries.js';
4
+ const MANIFEST_FILE = 'amalgm.app.json';
5
+ const SCHEMA_VERSION = 1;
6
+ const FIELDS = new Set([
7
+ 'schemaVersion',
8
+ 'name',
9
+ 'description',
10
+ 'buildCommand',
11
+ 'startCommand',
12
+ 'toolIds',
13
+ 'package',
14
+ ]);
15
+ const PACKAGE_FIELDS = new Set(['include', 'exclude']);
16
+ function object(value) {
17
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
18
+ }
19
+ function string(value) {
20
+ return typeof value === 'string' ? value.trim() : '';
21
+ }
22
+ function strings(value, field, errors, optional = false) {
23
+ if (value === undefined && optional)
24
+ return [];
25
+ if (!Array.isArray(value)) {
26
+ errors.push(`${field} must be an array of strings`);
27
+ return [];
28
+ }
29
+ const result = [];
30
+ value.forEach((entry, index) => {
31
+ const item = string(entry);
32
+ if (!item)
33
+ errors.push(`${field}[${index}] must be a non-empty string`);
34
+ else if (!result.includes(item))
35
+ result.push(item);
36
+ });
37
+ return result;
38
+ }
39
+ function unknown(value, allowed, prefix = '') {
40
+ if (!object(value))
41
+ return [];
42
+ return Object.keys(value)
43
+ .filter((key) => !allowed.has(key))
44
+ .map((key) => `${prefix}${key} is not supported`);
45
+ }
46
+ function normalizeManifest(value) {
47
+ if (!object(value))
48
+ throw new Error(`${MANIFEST_FILE} must contain an object`);
49
+ const errors = unknown(value, FIELDS);
50
+ if (value.schemaVersion !== SCHEMA_VERSION) {
51
+ errors.push(`schemaVersion must be ${SCHEMA_VERSION}`);
52
+ }
53
+ const name = string(value.name);
54
+ if (!name)
55
+ errors.push('name must be a non-empty string');
56
+ const description = value.description === undefined ? '' : string(value.description);
57
+ if (value.description !== undefined && typeof value.description !== 'string') {
58
+ errors.push('description must be a string');
59
+ }
60
+ const buildCommand = value.buildCommand == null ? null : string(value.buildCommand);
61
+ if (value.buildCommand != null && !buildCommand) {
62
+ errors.push('buildCommand must be a non-empty string or null');
63
+ }
64
+ const startCommand = string(value.startCommand);
65
+ if (!startCommand)
66
+ errors.push('startCommand must be a non-empty string');
67
+ const toolIds = strings(value.toolIds, 'toolIds', errors, true);
68
+ if (!object(value.package))
69
+ errors.push('package must be an object');
70
+ else
71
+ errors.push(...unknown(value.package, PACKAGE_FIELDS, 'package.'));
72
+ const packageDefinition = object(value.package) ? value.package : {};
73
+ const include = strings(packageDefinition.include, 'package.include', errors);
74
+ const exclude = strings(packageDefinition.exclude, 'package.exclude', errors, true);
75
+ if (include.length === 0)
76
+ errors.push('package.include must select at least one app file');
77
+ if (errors.length) {
78
+ throw new Error(`${MANIFEST_FILE} is invalid:\n- ${errors.join('\n- ')}`);
79
+ }
80
+ return {
81
+ schemaVersion: SCHEMA_VERSION,
82
+ name,
83
+ description,
84
+ buildCommand,
85
+ startCommand,
86
+ toolIds,
87
+ package: { include, exclude },
88
+ };
89
+ }
90
+ function readManifest(rootDir) {
91
+ const file = path.join(path.resolve(rootDir), MANIFEST_FILE);
92
+ if (!fs.existsSync(file))
93
+ throw new Error(`${MANIFEST_FILE} is required: ${file}`);
94
+ try {
95
+ return normalizeManifest(JSON.parse(fs.readFileSync(file, 'utf8')));
96
+ }
97
+ catch (error) {
98
+ const message = errorMessage(error);
99
+ if (message.startsWith(`${MANIFEST_FILE} is invalid`))
100
+ throw error;
101
+ throw new Error(`${MANIFEST_FILE} is not valid JSON: ${message}`);
102
+ }
103
+ }
104
+ export { MANIFEST_FILE, SCHEMA_VERSION, normalizeManifest, readManifest, };
105
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer, McpServerOptions } from './types.js';
2
+ declare function createMcpServer(options?: McpServerOptions): McpServer;
3
+ export { createMcpServer };
@@ -0,0 +1,65 @@
1
+ import readline from 'node:readline';
2
+ import { Apps } from './apps.js';
3
+ import { createMcpTools } from './mcp.js';
4
+ import { errorCode } from './boundaries.js';
5
+ function descriptor(tool) {
6
+ const { handler, ...definition } = tool;
7
+ return definition;
8
+ }
9
+ function createMcpServer(options = {}) {
10
+ const apps = options.apps || new Apps(options);
11
+ const tools = createMcpTools(apps);
12
+ const byName = new Map(tools.map((tool) => [tool.name, tool]));
13
+ async function handle(message) {
14
+ if (message.method === 'initialize') {
15
+ return {
16
+ protocolVersion: message.params?.protocolVersion || '2024-11-05',
17
+ capabilities: { tools: { listChanged: false } },
18
+ serverInfo: { name: 'amalgm-apps', version: '0.1.0' },
19
+ };
20
+ }
21
+ if (message.method === 'ping')
22
+ return {};
23
+ if (message.method === 'tools/list')
24
+ return { tools: tools.map(descriptor) };
25
+ if (message.method === 'tools/call') {
26
+ const tool = byName.get(message.params?.name);
27
+ if (!tool)
28
+ throw Object.assign(new Error(`Unknown tool: ${message.params?.name}`), { code: -32602 });
29
+ return tool.handler(message.params?.arguments || {});
30
+ }
31
+ if (message.method?.startsWith('notifications/'))
32
+ return undefined;
33
+ throw Object.assign(new Error(`Method not found: ${message.method}`), { code: -32601 });
34
+ }
35
+ async function start(input = process.stdin, output = process.stdout) {
36
+ await apps.boot();
37
+ const lines = readline.createInterface({ input, crlfDelay: Infinity });
38
+ lines.on('line', async (line) => {
39
+ let message;
40
+ try {
41
+ message = JSON.parse(line);
42
+ const result = await handle(message);
43
+ if (message.id === undefined || result === undefined)
44
+ return;
45
+ output.write(`${JSON.stringify({ jsonrpc: '2.0', id: message.id, result })}\n`);
46
+ }
47
+ catch (error) {
48
+ if (message?.id === undefined)
49
+ return;
50
+ output.write(`${JSON.stringify({
51
+ jsonrpc: '2.0',
52
+ id: message.id,
53
+ error: {
54
+ code: errorCode(error) || -32603,
55
+ message: error instanceof Error ? error.message : String(error),
56
+ },
57
+ })}\n`);
58
+ }
59
+ });
60
+ return lines;
61
+ }
62
+ return { apps, handle, start, tools };
63
+ }
64
+ export { createMcpServer };
65
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Apps } from './apps.js';
2
+ import type { McpTool } from './types.js';
3
+ declare function createMcpTools(apps: Apps): McpTool[];
4
+ export { createMcpTools };
package/dist/mcp.js ADDED
@@ -0,0 +1,171 @@
1
+ function text(value) {
2
+ return {
3
+ content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
4
+ };
5
+ }
6
+ function createMcpTools(apps) {
7
+ const action = (definition, handler) => ({
8
+ ...definition,
9
+ async handler(args = {}) {
10
+ try {
11
+ return text(await handler(args));
12
+ }
13
+ catch (error) {
14
+ return { ...text(error instanceof Error ? error.message : String(error)), isError: true };
15
+ }
16
+ },
17
+ });
18
+ const id = {
19
+ app_id: { type: 'string', description: 'Stable app id' },
20
+ };
21
+ return [
22
+ action({
23
+ name: 'apps_register',
24
+ description: 'Register, build, and start an app from a directory containing amalgm.app.json.',
25
+ inputSchema: {
26
+ type: 'object',
27
+ additionalProperties: false,
28
+ required: ['cwd'],
29
+ properties: {
30
+ cwd: { type: 'string' },
31
+ port: { type: 'integer' },
32
+ connect_dns: { type: 'boolean' },
33
+ autostart: { type: 'boolean' },
34
+ keep_alive: { type: 'boolean' },
35
+ },
36
+ },
37
+ }, (args) => apps.register(args)),
38
+ action({
39
+ name: 'apps_validate',
40
+ description: 'Validate and inspect an app package without changing state.',
41
+ inputSchema: {
42
+ type: 'object',
43
+ additionalProperties: false,
44
+ required: ['cwd'],
45
+ properties: { cwd: { type: 'string' } },
46
+ },
47
+ }, ({ cwd }) => apps.validate(cwd)),
48
+ action({
49
+ name: 'apps_list',
50
+ description: 'List registered apps and their active releases.',
51
+ inputSchema: { type: 'object', additionalProperties: false, properties: {} },
52
+ }, () => apps.list()),
53
+ action({
54
+ name: 'apps_show',
55
+ description: 'Get one registered app.',
56
+ inputSchema: {
57
+ type: 'object',
58
+ additionalProperties: false,
59
+ required: ['app_id'],
60
+ properties: id,
61
+ },
62
+ }, ({ app_id }) => {
63
+ const app = apps.get(app_id);
64
+ if (!app)
65
+ throw new Error(`App not found: ${app_id}`);
66
+ return app;
67
+ }),
68
+ action({
69
+ name: 'apps_deploy',
70
+ description: 'Build an immutable release and activate it. The current process stays up through the build.',
71
+ inputSchema: {
72
+ type: 'object',
73
+ additionalProperties: false,
74
+ required: ['app_id'],
75
+ properties: { ...id, cwd: { type: 'string' } },
76
+ },
77
+ }, ({ app_id, cwd }) => apps.deploy(app_id, { cwd })),
78
+ action({
79
+ name: 'apps_versions',
80
+ description: 'List immutable releases for an app.',
81
+ inputSchema: {
82
+ type: 'object',
83
+ additionalProperties: false,
84
+ required: ['app_id'],
85
+ properties: id,
86
+ },
87
+ }, ({ app_id }) => apps.versions(app_id)),
88
+ action({
89
+ name: 'apps_deployments',
90
+ description: 'List append-only deployment history for an app.',
91
+ inputSchema: {
92
+ type: 'object',
93
+ additionalProperties: false,
94
+ required: ['app_id'],
95
+ properties: id,
96
+ },
97
+ }, ({ app_id }) => apps.deployments(app_id)),
98
+ action({
99
+ name: 'apps_rollback',
100
+ description: 'Activate an earlier immutable release as a new deployment.',
101
+ inputSchema: {
102
+ type: 'object',
103
+ additionalProperties: false,
104
+ required: ['app_id'],
105
+ properties: { ...id, release_id: { type: 'string' } },
106
+ },
107
+ }, ({ app_id, release_id }) => apps.rollback(app_id, { releaseId: release_id })),
108
+ action({
109
+ name: 'apps_rename',
110
+ description: 'Rename an app without changing its id, route, or releases.',
111
+ inputSchema: {
112
+ type: 'object',
113
+ additionalProperties: false,
114
+ required: ['app_id', 'name'],
115
+ properties: { ...id, name: { type: 'string' } },
116
+ },
117
+ }, ({ app_id, name }) => apps.rename(app_id, name)),
118
+ action({
119
+ name: 'apps_start',
120
+ description: 'Start the active deployment.',
121
+ inputSchema: {
122
+ type: 'object',
123
+ additionalProperties: false,
124
+ required: ['app_id'],
125
+ properties: id,
126
+ },
127
+ }, ({ app_id }) => apps.start(app_id)),
128
+ action({
129
+ name: 'apps_stop',
130
+ description: 'Stop an app and preserve its releases.',
131
+ inputSchema: {
132
+ type: 'object',
133
+ additionalProperties: false,
134
+ required: ['app_id'],
135
+ properties: id,
136
+ },
137
+ }, ({ app_id }) => apps.stop(app_id)),
138
+ action({
139
+ name: 'apps_connect_dns',
140
+ description: 'Make a running healthy app eligible for route publication.',
141
+ inputSchema: {
142
+ type: 'object',
143
+ additionalProperties: false,
144
+ required: ['app_id'],
145
+ properties: id,
146
+ },
147
+ }, ({ app_id }) => apps.connectDns(app_id)),
148
+ action({
149
+ name: 'apps_disconnect_dns',
150
+ description: 'Remove route eligibility without stopping the app.',
151
+ inputSchema: {
152
+ type: 'object',
153
+ additionalProperties: false,
154
+ required: ['app_id'],
155
+ properties: id,
156
+ },
157
+ }, ({ app_id }) => apps.disconnectDns(app_id)),
158
+ action({
159
+ name: 'apps_delete',
160
+ description: 'Stop and delete an app, its releases, deployments, logs, and data.',
161
+ inputSchema: {
162
+ type: 'object',
163
+ additionalProperties: false,
164
+ required: ['app_id'],
165
+ properties: id,
166
+ },
167
+ }, ({ app_id }) => apps.delete(app_id)),
168
+ ];
169
+ }
170
+ export { createMcpTools };
171
+ //# sourceMappingURL=mcp.js.map