@hasna/machines 0.0.48 → 0.0.49

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/dist/storage.js CHANGED
@@ -1,35 +1,5 @@
1
1
  // @bun
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
2
  var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- function __accessProp(key) {
8
- return this[key];
9
- }
10
- var __toESMCache_node;
11
- var __toESMCache_esm;
12
- var __toESM = (mod, isNodeMode, target) => {
13
- var canCache = mod != null && typeof mod === "object";
14
- if (canCache) {
15
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
- var cached = cache.get(mod);
17
- if (cached)
18
- return cached;
19
- }
20
- target = mod != null ? __create(__getProtoOf(mod)) : {};
21
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
- for (let key of __getOwnPropNames(mod))
23
- if (!__hasOwnProp.call(to, key))
24
- __defProp(to, key, {
25
- get: __accessProp.bind(mod, key),
26
- enumerable: true
27
- });
28
- if (canCache)
29
- cache.set(mod, to);
30
- return to;
31
- };
32
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
3
  var __returnValue = (v) => v;
34
4
  function __exportSetter(name, newValue) {
35
5
  this[name] = __returnValue.bind(null, newValue);
@@ -206,51 +176,6 @@ function getAdapter(path = getDbPath()) {
206
176
  function getDb(path = getDbPath()) {
207
177
  return getAdapter(path).raw;
208
178
  }
209
- function closeDb() {
210
- if (adapter) {
211
- adapter.close();
212
- adapter = null;
213
- }
214
- }
215
- function upsertHeartbeat(machineId, pid = process.pid, status = "online", metadata = {}) {
216
- const db = getDb();
217
- db.query(`INSERT INTO agent_heartbeats (
218
- machine_id,
219
- pid,
220
- status,
221
- updated_at,
222
- daemon_version,
223
- agent_mode,
224
- platform,
225
- os_version,
226
- os_build,
227
- arch,
228
- uptime_seconds,
229
- tool_versions_json,
230
- tailscale_json,
231
- storage_sync_status,
232
- storage_sync_last_error,
233
- doctor_summary_json,
234
- private_metadata
235
- )
236
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
237
- ON CONFLICT(machine_id, pid) DO UPDATE SET
238
- status = excluded.status,
239
- updated_at = excluded.updated_at,
240
- daemon_version = excluded.daemon_version,
241
- agent_mode = excluded.agent_mode,
242
- platform = excluded.platform,
243
- os_version = excluded.os_version,
244
- os_build = excluded.os_build,
245
- arch = excluded.arch,
246
- uptime_seconds = excluded.uptime_seconds,
247
- tool_versions_json = excluded.tool_versions_json,
248
- tailscale_json = excluded.tailscale_json,
249
- storage_sync_status = excluded.storage_sync_status,
250
- storage_sync_last_error = excluded.storage_sync_last_error,
251
- doctor_summary_json = excluded.doctor_summary_json,
252
- private_metadata = excluded.private_metadata`).run(machineId, pid, status, new Date().toISOString(), metadata.daemonVersion ?? null, metadata.agentMode ?? null, metadata.platform ?? null, metadata.osVersion ?? null, metadata.osBuild ?? null, metadata.arch ?? null, metadata.uptimeSeconds == null ? null : Math.max(0, Math.floor(metadata.uptimeSeconds)), metadata.toolVersions ? JSON.stringify(metadata.toolVersions) : null, metadata.tailscale ? JSON.stringify(metadata.tailscale) : null, metadata.storageSyncStatus ?? null, metadata.storageSyncLastError ?? null, metadata.doctorSummary ? JSON.stringify(metadata.doctorSummary) : null, metadata.privateMetadata ? 1 : 0);
253
- }
254
179
  function getLocalMachineId() {
255
180
  return process.env["HASNA_MACHINES_MACHINE_ID"] || hostname();
256
181
  }
@@ -280,12 +205,6 @@ function countRuns(table) {
280
205
  const row = db.query(`SELECT COUNT(*) as count FROM ${table}`).get();
281
206
  return row.count;
282
207
  }
283
- function setHeartbeatStatus(machineId, pid, status) {
284
- const db = getDb();
285
- db.query(`UPDATE agent_heartbeats
286
- SET status = ?, updated_at = ?
287
- WHERE machine_id = ? AND pid = ?`).run(status, new Date().toISOString(), machineId, pid);
288
- }
289
208
  function recordSetupRun(machineId, status, details) {
290
209
  const db = getDb();
291
210
  const now = new Date().toISOString();
@@ -359,6 +278,8 @@ var PG_MIGRATIONS = [
359
278
 
360
279
  // src/remote-storage.ts
361
280
  import pg from "pg";
281
+ var MACHINES_DATABASE_ALLOW_INSECURE_TLS_ENV = "HASNA_MACHINES_ALLOW_INSECURE_DATABASE_TLS";
282
+ var MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED_ENV = "HASNA_MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED";
362
283
  function translatePlaceholders(sql) {
363
284
  let index = 0;
364
285
  return sql.replace(/\?/g, () => `$${++index}`);
@@ -367,7 +288,18 @@ function normalizeParams(params) {
367
288
  const flat = params.length === 1 && Array.isArray(params[0]) ? params[0] : params;
368
289
  return flat.map((value) => value === undefined ? null : value);
369
290
  }
370
- function sslConfigFor(connectionString) {
291
+ function envFlag(env, name) {
292
+ const value = env[name]?.trim().toLowerCase();
293
+ return value === "1" || value === "true" || value === "yes" || value === "on";
294
+ }
295
+ function isLoopbackHost(hostname2) {
296
+ const normalized = hostname2.replace(/^\[|\]$/g, "").toLowerCase();
297
+ return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
298
+ }
299
+ function allowsLocalInsecureTls(url, env) {
300
+ return isLoopbackHost(url.hostname) && envFlag(env, MACHINES_DATABASE_ALLOW_INSECURE_TLS_ENV);
301
+ }
302
+ function sslConfigFor(connectionString, env = process.env) {
371
303
  let url;
372
304
  try {
373
305
  url = new URL(connectionString);
@@ -376,12 +308,21 @@ function sslConfigFor(connectionString) {
376
308
  }
377
309
  const sslMode = url.searchParams.get("sslmode")?.toLowerCase();
378
310
  const ssl = url.searchParams.get("ssl")?.toLowerCase();
379
- if (sslMode === "disable" || ssl === "false")
380
- return;
381
- if (sslMode === "no-verify" || process.env["HASNA_MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED"] === "0") {
311
+ const rejectUnauthorizedOverride = env[MACHINES_DATABASE_SSL_REJECT_UNAUTHORIZED_ENV]?.trim() === "0";
312
+ if (sslMode === "disable" || ssl === "false") {
313
+ if (allowsLocalInsecureTls(url, env))
314
+ return;
315
+ throw new Error(`Insecure PostgreSQL TLS mode is rejected for remote storage; use sslmode=require or set ${MACHINES_DATABASE_ALLOW_INSECURE_TLS_ENV}=1 only for loopback development databases.`);
316
+ }
317
+ if (sslMode === "no-verify" || rejectUnauthorizedOverride) {
318
+ if (!allowsLocalInsecureTls(url, env)) {
319
+ throw new Error(`PostgreSQL TLS certificate verification cannot be disabled for remote storage; set ${MACHINES_DATABASE_ALLOW_INSECURE_TLS_ENV}=1 only for loopback development databases.`);
320
+ }
382
321
  return { rejectUnauthorized: false };
383
322
  }
384
- return sslMode || ssl === "true" ? { rejectUnauthorized: true } : undefined;
323
+ if (sslMode || ssl === "true")
324
+ return { rejectUnauthorized: true };
325
+ return isLoopbackHost(url.hostname) ? undefined : { rejectUnauthorized: true };
385
326
  }
386
327
 
387
328
  class PgAdapterAsync {
@@ -666,16 +607,376 @@ function coerceForSqlite(value) {
666
607
  return JSON.stringify(value);
667
608
  return String(value);
668
609
  }
610
+
611
+ // src/commands/mutation-approval.ts
612
+ import { createHash, createHmac, randomUUID, timingSafeEqual } from "crypto";
613
+ import { resolve as resolve2 } from "path";
614
+ var MUTATION_APPROVAL_FLAG_ENV = "HASNA_MACHINES_ALLOW_MUTATIONS";
615
+ var LEGACY_MUTATION_APPROVAL_FLAG_ENV = "HASNA_MACHINES_MUTATION_APPROVAL";
616
+ var MUTATION_APPROVAL_TOKEN_ENV = "HASNA_MACHINES_MUTATION_TOKEN";
617
+ var MUTATION_APPROVAL_CALLER_ENV = "HASNA_MACHINES_MUTATION_CALLER_ID";
618
+ var MUTATION_APPROVAL_RUN_ENV = "HASNA_MACHINES_MUTATION_RUN_ID";
619
+ var MUTATION_APPROVAL_REPLAY_PATH_ENV = "HASNA_MACHINES_MUTATION_REPLAY_PATH";
620
+ var TOKEN_PREFIX = "machines-mut-v1";
621
+ var DEFAULT_TOKEN_TTL_MS = 5 * 60 * 1000;
622
+ var MAX_TOKEN_TTL_MS = 5 * 60 * 1000;
623
+ var MAX_CLOCK_SKEW_MS = 30000;
624
+ var trustedSdkMutationApprovals = new WeakSet;
625
+ function isTrustedSdkMutationApproval(approval) {
626
+ return typeof approval === "object" && approval !== null && trustedSdkMutationApprovals.has(approval);
627
+ }
628
+ function isTruthy(value) {
629
+ return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
630
+ }
631
+ function nowMs(now) {
632
+ if (typeof now === "number")
633
+ return now;
634
+ if (now instanceof Date)
635
+ return now.getTime();
636
+ return Date.now();
637
+ }
638
+ function signingSecret(env, explicitSecret) {
639
+ return explicitSecret?.trim() || env[MUTATION_APPROVAL_TOKEN_ENV]?.trim();
640
+ }
641
+ function base64Url(value) {
642
+ return Buffer.from(value).toString("base64url");
643
+ }
644
+ function hmac(payload, secret) {
645
+ return createHmac("sha256", secret).update(payload).digest("base64url");
646
+ }
647
+ function sha256Hex(payload) {
648
+ return createHash("sha256").update(payload).digest("hex");
649
+ }
650
+ function replayDbPath(env) {
651
+ const configured = env[MUTATION_APPROVAL_REPLAY_PATH_ENV]?.trim();
652
+ return configured ? resolve2(configured) : undefined;
653
+ }
654
+ function replayNonceKey(claims) {
655
+ return sha256Hex(JSON.stringify({ nonce: claims.nonce }));
656
+ }
657
+ function recordReplayNonce(env, claims, tokenPayload, now) {
658
+ const dbPath = replayDbPath(env);
659
+ if (!dbPath)
660
+ return;
661
+ if (!claims.nonce) {
662
+ return { approved: false, reason: "approval_token nonce claim is required for replay protection." };
663
+ }
664
+ try {
665
+ const db = getDb(dbPath);
666
+ db.query("DELETE FROM mutation_approval_nonces WHERE expires_at <= ?").run(now);
667
+ const result = db.query(`
668
+ INSERT OR IGNORE INTO mutation_approval_nonces (
669
+ nonce_sha256,
670
+ token_sha256,
671
+ surface,
672
+ operation,
673
+ caller_id,
674
+ run_id,
675
+ transport,
676
+ expires_at,
677
+ used_at
678
+ )
679
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
680
+ `).run(replayNonceKey(claims), sha256Hex(tokenPayload), claims.surface, claims.operation, claims.callerId ?? "", claims.runId ?? "", claims.transport ?? "", claims.expiresAt, now);
681
+ if (result.changes !== 1) {
682
+ return { approved: false, reason: "approval_token nonce has already been used." };
683
+ }
684
+ return;
685
+ } catch (error) {
686
+ const message = error instanceof Error ? error.message : String(error);
687
+ return { approved: false, reason: `approval_token replay store is unavailable: ${message}` };
688
+ }
689
+ }
690
+ function safeEqual(left, right) {
691
+ const leftBuffer = Buffer.from(left);
692
+ const rightBuffer = Buffer.from(right);
693
+ return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
694
+ }
695
+ function normalizeScope(scope) {
696
+ return {
697
+ surface: scope.surface,
698
+ operation: scope.operation,
699
+ machineId: scope.machineId || undefined,
700
+ resourceId: scope.resourceId || undefined,
701
+ callerId: scope.callerId || undefined,
702
+ runId: scope.runId || undefined,
703
+ transport: scope.transport || undefined,
704
+ argsSha256: scope.argsSha256 || (scope.args === undefined ? undefined : mutationArgsSha256(scope.args))
705
+ };
706
+ }
707
+ function canonicalizeMutationArg(value, inArray = false) {
708
+ if (value === undefined)
709
+ return inArray ? null : undefined;
710
+ if (value === null || typeof value === "boolean" || typeof value === "string")
711
+ return value;
712
+ if (typeof value === "number")
713
+ return Number.isFinite(value) ? value : null;
714
+ if (Array.isArray(value)) {
715
+ return value.map((entry) => canonicalizeMutationArg(entry, true) ?? null);
716
+ }
717
+ if (value instanceof Date)
718
+ return value.toISOString();
719
+ if (typeof value === "object") {
720
+ const result = {};
721
+ for (const key of Object.keys(value).sort()) {
722
+ if (key === "approval_token" || key === "approvalToken")
723
+ continue;
724
+ const canonicalValue = canonicalizeMutationArg(value[key]);
725
+ if (canonicalValue !== undefined)
726
+ result[key] = canonicalValue;
727
+ }
728
+ return result;
729
+ }
730
+ return inArray ? null : undefined;
731
+ }
732
+ function canonicalMutationArgs(value) {
733
+ return JSON.stringify(canonicalizeMutationArg(value) ?? {});
734
+ }
735
+ function mutationArgsSha256(value) {
736
+ return sha256Hex(canonicalMutationArgs(value));
737
+ }
738
+ function stripPlanRuntimeFields(value) {
739
+ if (Array.isArray(value))
740
+ return value.map(stripPlanRuntimeFields);
741
+ if (value instanceof Date)
742
+ return value;
743
+ if (value && typeof value === "object") {
744
+ const result = {};
745
+ for (const [key, entry] of Object.entries(value)) {
746
+ if (key === "planDigest" || key === "plan_digest" || key === "mode" || key === "executed")
747
+ continue;
748
+ result[key] = stripPlanRuntimeFields(entry);
749
+ }
750
+ return result;
751
+ }
752
+ return value;
753
+ }
754
+ function mutationPlanDigest(plan) {
755
+ return mutationArgsSha256(stripPlanRuntimeFields(plan));
756
+ }
757
+ function attachMutationPlanDigest(plan) {
758
+ return {
759
+ ...plan,
760
+ planDigest: mutationPlanDigest(plan)
761
+ };
762
+ }
763
+ function assertMutationPlanDigest(plan, expectedPlanDigest) {
764
+ if (expectedPlanDigest && mutationPlanDigest(plan) !== expectedPlanDigest) {
765
+ throw new Error("Approved plan digest does not match the current execution plan.");
766
+ }
767
+ }
768
+ function createMutationApprovalToken(scope, options = {}) {
769
+ const env = options.env ?? process.env;
770
+ const secret = signingSecret(env, options.secret);
771
+ if (!secret)
772
+ throw new Error(`${MUTATION_APPROVAL_TOKEN_ENV} is required to sign mutation approval tokens.`);
773
+ const issuedAt = nowMs(options.now);
774
+ const claims = {
775
+ version: 1,
776
+ ...normalizeScope(scope),
777
+ callerId: scope.callerId,
778
+ runId: scope.runId,
779
+ transport: scope.transport,
780
+ issuedAt,
781
+ expiresAt: issuedAt + Math.max(1, options.ttlMs ?? DEFAULT_TOKEN_TTL_MS),
782
+ nonce: options.nonce ?? randomUUID()
783
+ };
784
+ claims.args_sha256 = claims.argsSha256;
785
+ delete claims.args;
786
+ delete claims.argsSha256;
787
+ const payload = base64Url(JSON.stringify(claims));
788
+ return `${TOKEN_PREFIX}.${payload}.${hmac(payload, secret)}`;
789
+ }
790
+ function parseToken(token) {
791
+ if (!token)
792
+ return null;
793
+ const parts = token.split(".");
794
+ if (parts.length !== 3 || parts[0] !== TOKEN_PREFIX)
795
+ return null;
796
+ try {
797
+ const claims = JSON.parse(Buffer.from(parts[1] ?? "", "base64url").toString("utf8"));
798
+ return { payload: parts[1] ?? "", signature: parts[2] ?? "", claims };
799
+ } catch {
800
+ return null;
801
+ }
802
+ }
803
+ function claimMatches(expected, actual) {
804
+ if (expected === undefined)
805
+ return actual === undefined;
806
+ return actual === expected;
807
+ }
808
+ function verifyMutationApprovalToken(options) {
809
+ const env = options.env ?? process.env;
810
+ const secret = signingSecret(env);
811
+ if (!secret)
812
+ return { approved: false, reason: `${MUTATION_APPROVAL_TOKEN_ENV} is not configured.` };
813
+ const parsed = parseToken(options.approvalToken);
814
+ if (!parsed)
815
+ return { approved: false, reason: "approval_token is not a scoped mutation token." };
816
+ if (!safeEqual(hmac(parsed.payload, secret), parsed.signature)) {
817
+ return { approved: false, reason: "approval_token signature is invalid." };
818
+ }
819
+ const claims = parsed.claims;
820
+ if (claims.version !== 1)
821
+ return { approved: false, reason: "approval_token version is unsupported." };
822
+ if (!claims.callerId || !claims.runId) {
823
+ return { approved: false, reason: "approval_token must include caller and run claims." };
824
+ }
825
+ if (!claims.transport) {
826
+ return { approved: false, reason: "approval_token must include a transport claim." };
827
+ }
828
+ if (!Number.isFinite(claims.expiresAt) || claims.expiresAt <= nowMs(options.now)) {
829
+ return { approved: false, reason: "approval_token is expired." };
830
+ }
831
+ const now = nowMs(options.now);
832
+ if (!Number.isFinite(claims.issuedAt) || claims.issuedAt > now + MAX_CLOCK_SKEW_MS) {
833
+ return { approved: false, reason: "approval_token issue time is invalid." };
834
+ }
835
+ if (claims.expiresAt - claims.issuedAt > MAX_TOKEN_TTL_MS) {
836
+ return { approved: false, reason: "approval_token TTL is too long." };
837
+ }
838
+ for (const key of ["surface", "operation", "machineId", "resourceId", "transport"]) {
839
+ if (!claimMatches(options[key], claims[key])) {
840
+ return { approved: false, reason: `approval_token ${key} claim does not match this mutation.` };
841
+ }
842
+ }
843
+ for (const key of ["callerId", "runId"]) {
844
+ if (options[key] !== undefined && options[key] !== claims[key]) {
845
+ return { approved: false, reason: `approval_token ${key} claim does not match this mutation.` };
846
+ }
847
+ }
848
+ const expectedArgsSha256 = options.argsSha256 || (options.args === undefined ? undefined : mutationArgsSha256(options.args));
849
+ if (expectedArgsSha256 !== undefined && claims.args_sha256 !== expectedArgsSha256) {
850
+ return { approved: false, reason: "approval_token args_sha256 claim does not match this mutation." };
851
+ }
852
+ const replayDecision = recordReplayNonce(env, claims, parsed.payload, now);
853
+ if (replayDecision)
854
+ return replayDecision;
855
+ return { approved: true, claims };
856
+ }
857
+ function isMutationApproved(options = {}) {
858
+ const env = options.env ?? process.env;
859
+ const surface = options.surface ?? "cli";
860
+ if (surface === "mcp") {
861
+ if (!options.operation)
862
+ return false;
863
+ return verifyMutationApprovalToken({
864
+ surface,
865
+ operation: options.operation,
866
+ machineId: options.machineId,
867
+ resourceId: options.resourceId,
868
+ callerId: options.callerId,
869
+ runId: options.runId,
870
+ transport: options.transport ?? "mcp",
871
+ args: options.args,
872
+ argsSha256: options.argsSha256,
873
+ approvalToken: options.approvalToken,
874
+ env,
875
+ now: options.now
876
+ }).approved;
877
+ }
878
+ if (options.approvalToken) {
879
+ const decision = options.operation ? verifyMutationApprovalToken({
880
+ surface,
881
+ operation: options.operation,
882
+ machineId: options.machineId,
883
+ resourceId: options.resourceId,
884
+ callerId: options.callerId,
885
+ runId: options.runId,
886
+ transport: options.transport ?? surface,
887
+ args: options.args,
888
+ argsSha256: options.argsSha256,
889
+ approvalToken: options.approvalToken,
890
+ env,
891
+ now: options.now
892
+ }) : { approved: false };
893
+ if (decision.approved)
894
+ return true;
895
+ if (env[MUTATION_APPROVAL_TOKEN_ENV]?.trim())
896
+ return false;
897
+ }
898
+ return isTruthy(env[MUTATION_APPROVAL_FLAG_ENV]) || isTruthy(env[LEGACY_MUTATION_APPROVAL_FLAG_ENV]);
899
+ }
900
+ function assertMutationApproved(options) {
901
+ if (isMutationApproved(options)) {
902
+ return;
903
+ }
904
+ const env = options.env ?? process.env;
905
+ const tokenConfigured = Boolean(env[MUTATION_APPROVAL_TOKEN_ENV]?.trim());
906
+ const approvalHint = options.surface === "mcp" ? `pass a scoped approval_token signed with ${MUTATION_APPROVAL_TOKEN_ENV}` : tokenConfigured ? `pass a scoped approval_token signed with ${MUTATION_APPROVAL_TOKEN_ENV} or set ${MUTATION_APPROVAL_FLAG_ENV}=1 for a trusted local session` : `set ${MUTATION_APPROVAL_FLAG_ENV}=1 for a trusted local session or configure ${MUTATION_APPROVAL_TOKEN_ENV}`;
907
+ throw new Error(`Fleet mutation blocked: ${options.surface}.${options.operation} requires operator approval; ${approvalHint}.`);
908
+ }
909
+ function assertSdkMutationApproved(scope, options = {}) {
910
+ if (isTrustedSdkMutationApproval(options.trustedLocalMutation))
911
+ return;
912
+ const decision = verifyMutationApprovalToken({
913
+ surface: "sdk",
914
+ operation: scope.operation,
915
+ machineId: scope.machineId,
916
+ resourceId: scope.resourceId,
917
+ callerId: options.callerId,
918
+ runId: options.runId,
919
+ transport: "sdk",
920
+ args: scope.args,
921
+ argsSha256: scope.argsSha256,
922
+ approvalToken: options.approvalToken,
923
+ env: process.env
924
+ });
925
+ if (decision.approved)
926
+ return;
927
+ throw new Error(`Fleet mutation blocked: sdk.${scope.operation} requires a scoped SDK approval token.`);
928
+ }
929
+
930
+ // src/storage.ts
931
+ function storageArgs(options = {}) {
932
+ return {
933
+ tables: options.tables?.length ? [...options.tables] : null
934
+ };
935
+ }
936
+ function storageResourceId(operation, options = {}) {
937
+ return `storage:${operation}:${mutationArgsSha256(storageArgs(options))}`;
938
+ }
939
+ async function runStorageMigrations2(remote, options = {}) {
940
+ assertSdkMutationApproved({
941
+ operation: "machines_storage_migrate",
942
+ resourceId: "storage:migrations",
943
+ args: {}
944
+ }, options);
945
+ return runStorageMigrations(remote);
946
+ }
947
+ async function storagePush2(options = {}) {
948
+ assertSdkMutationApproved({
949
+ operation: "machines_storage_push",
950
+ resourceId: storageResourceId("push", options),
951
+ args: storageArgs(options)
952
+ }, options);
953
+ return storagePush({ tables: options.tables });
954
+ }
955
+ async function storagePull2(options = {}) {
956
+ assertSdkMutationApproved({
957
+ operation: "machines_storage_pull",
958
+ resourceId: storageResourceId("pull", options),
959
+ args: storageArgs(options)
960
+ }, options);
961
+ return storagePull({ tables: options.tables });
962
+ }
963
+ async function storageSync2(options = {}) {
964
+ assertSdkMutationApproved({
965
+ operation: "machines_storage_sync",
966
+ resourceId: storageResourceId("sync", options),
967
+ args: storageArgs(options)
968
+ }, options);
969
+ return storageSync({ tables: options.tables });
970
+ }
669
971
  export {
670
- storageSync,
671
- storagePush,
672
- storagePull,
673
- runStorageMigrations,
972
+ storageSync2 as storageSync,
973
+ storagePush2 as storagePush,
974
+ storagePull2 as storagePull,
975
+ runStorageMigrations2 as runStorageMigrations,
674
976
  resolveTables,
675
977
  parseStorageTables,
676
978
  getSyncMetaAll,
677
979
  getStorageStatus,
678
- getStoragePg,
679
980
  getStorageMode,
680
981
  getStorageDatabaseUrl,
681
982
  getStorageDatabaseEnvName,
@@ -683,7 +984,6 @@ export {
683
984
  STORAGE_TABLES,
684
985
  STORAGE_MODE_ENV,
685
986
  STORAGE_DATABASE_ENV,
686
- PgAdapterAsync,
687
987
  PG_MIGRATIONS,
688
988
  MACHINES_STORAGE_TABLES,
689
989
  MACHINES_STORAGE_MODE_FALLBACK_ENV,
package/dist/types.d.ts CHANGED
@@ -211,8 +211,15 @@ export interface SelfTestCheck {
211
211
  summary: string;
212
212
  detail: string;
213
213
  }
214
+ export interface SelfTestCounts {
215
+ ok: number;
216
+ warn: number;
217
+ fail: number;
218
+ }
214
219
  export interface SelfTestResult {
215
220
  machineId: string;
221
+ overall: SelfTestCheck["status"];
222
+ counts: SelfTestCounts;
216
223
  checks: SelfTestCheck[];
217
224
  }
218
225
  export interface ClipboardEntry {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/machines",
3
- "version": "0.0.48",
3
+ "version": "0.0.49",
4
4
  "description": "Machine fleet management CLI + MCP for developers",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",