@ralphkrauss/codex-account-switcher 0.1.8 → 0.2.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.
package/src/remote.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { randomBytes } from 'node:crypto';
2
+ import { createHash, randomBytes, randomUUID } from 'node:crypto';
3
3
  import { constants as fsConstants } from 'node:fs';
4
4
  import {
5
5
  access,
@@ -28,6 +28,8 @@ export const REMOTE_CONFIG_VERSION = 1;
28
28
  export const DEFAULT_ONEPASSWORD_ITEM_PREFIX = 'cx-';
29
29
  export const ONEPASSWORD_BACKEND = '1password';
30
30
  export const ONEPASSWORD_AUTH_FIELD = 'auth_json';
31
+ export const REMOTE_METADATA_FIELD = 'cx_metadata';
32
+ export const LOCAL_SYNC_METADATA_VERSION = 1;
31
33
 
32
34
  export type RemoteBackend = typeof ONEPASSWORD_BACKEND;
33
35
  export type RemotePresence = 'present' | 'missing' | 'unknown';
@@ -91,6 +93,8 @@ export interface SyncPullResult {
91
93
  readonly overwritten: boolean;
92
94
  }
93
95
 
96
+ export type SyncState = 'in-sync' | 'local-newer' | 'remote-newer' | 'diverged' | 'unknown';
97
+
94
98
  export interface SyncStatusAccount {
95
99
  readonly account: string;
96
100
  readonly item: string | null;
@@ -102,6 +106,38 @@ export interface SyncStatusAccount {
102
106
  readonly presence: RemotePresence;
103
107
  readonly error: string | null;
104
108
  };
109
+ readonly sync: {
110
+ readonly state: SyncState;
111
+ readonly error: string | null;
112
+ };
113
+ }
114
+
115
+ export interface RemoteAuthMetadata {
116
+ readonly version: 1;
117
+ readonly account: string;
118
+ readonly authJsonSha256: string;
119
+ readonly updatedAt: string;
120
+ readonly deviceId?: string;
121
+ }
122
+
123
+ export interface LocalSyncMetadata {
124
+ readonly version: typeof LOCAL_SYNC_METADATA_VERSION;
125
+ readonly backend: RemoteBackend;
126
+ readonly account: string;
127
+ readonly vault?: string;
128
+ readonly item?: string;
129
+ readonly remoteAuthJsonSha256: string;
130
+ readonly lastSyncedAuthJsonSha256: string;
131
+ readonly lastSyncedAt: string;
132
+ readonly deviceId: string;
133
+ }
134
+
135
+ export interface AutoSyncResult {
136
+ readonly action: 'pulled' | 'pushed' | 'skipped';
137
+ readonly account: string;
138
+ readonly reason?: string;
139
+ readonly item?: string;
140
+ readonly backend?: RemoteBackend;
105
141
  }
106
142
 
107
143
  export interface SyncStatus {
@@ -211,6 +247,98 @@ async function writeFilePrivate(destination: string, contents: string): Promise<
211
247
  }
212
248
  }
213
249
 
250
+ function sha256Hex(value: string): string {
251
+ return createHash('sha256').update(value).digest('hex');
252
+ }
253
+
254
+ function metadataAssignment(metadata: RemoteAuthMetadata): string {
255
+ return `${REMOTE_METADATA_FIELD}=${JSON.stringify(metadata)}`;
256
+ }
257
+
258
+ function buildRemoteAuthMetadata(account: string, authJson: string): RemoteAuthMetadata {
259
+ return {
260
+ version: 1,
261
+ account,
262
+ authJsonSha256: sha256Hex(authJson),
263
+ updatedAt: new Date().toISOString(),
264
+ deviceId: getLocalDeviceId(),
265
+ };
266
+ }
267
+
268
+ function getLocalDeviceId(): string {
269
+ const key = 'CX_DEVICE_ID';
270
+ const existing = process.env[key];
271
+ if (existing && existing.trim().length > 0) {
272
+ return existing.trim();
273
+ }
274
+ return randomUUID();
275
+ }
276
+
277
+ function getLocalSyncMetadataPath(paths: CodexPaths, account: string): string {
278
+ return join(paths.accountsDir, '.sync', `${validateAccountName(account)}.json`);
279
+ }
280
+
281
+ async function readLocalSyncMetadata(paths: CodexPaths, account: string): Promise<LocalSyncMetadata | null> {
282
+ let raw: string;
283
+ try {
284
+ raw = await readFile(getLocalSyncMetadataPath(paths, account), 'utf8');
285
+ } catch (error) {
286
+ if (isNotFoundError(error)) {
287
+ return null;
288
+ }
289
+ throw error;
290
+ }
291
+
292
+ try {
293
+ const parsed = JSON.parse(raw) as unknown;
294
+ if (!isRecord(parsed)
295
+ || parsed.version !== LOCAL_SYNC_METADATA_VERSION
296
+ || parsed.backend !== ONEPASSWORD_BACKEND
297
+ || parsed.account !== account
298
+ || typeof parsed.remoteAuthJsonSha256 !== 'string'
299
+ || typeof parsed.lastSyncedAuthJsonSha256 !== 'string'
300
+ || typeof parsed.lastSyncedAt !== 'string'
301
+ || typeof parsed.deviceId !== 'string') {
302
+ return null;
303
+ }
304
+ return {
305
+ version: LOCAL_SYNC_METADATA_VERSION,
306
+ backend: ONEPASSWORD_BACKEND,
307
+ account,
308
+ ...(typeof parsed.vault === 'string' ? { vault: parsed.vault } : {}),
309
+ ...(typeof parsed.item === 'string' ? { item: parsed.item } : {}),
310
+ remoteAuthJsonSha256: parsed.remoteAuthJsonSha256,
311
+ lastSyncedAuthJsonSha256: parsed.lastSyncedAuthJsonSha256,
312
+ lastSyncedAt: parsed.lastSyncedAt,
313
+ deviceId: parsed.deviceId,
314
+ };
315
+ } catch {
316
+ return null;
317
+ }
318
+ }
319
+
320
+ async function writeLocalSyncMetadata(
321
+ paths: CodexPaths,
322
+ config: RemoteConfig,
323
+ account: string,
324
+ authJson: string,
325
+ remoteMetadata?: RemoteAuthMetadata | null,
326
+ ): Promise<void> {
327
+ const hash = sha256Hex(authJson);
328
+ const metadata: LocalSyncMetadata = {
329
+ version: LOCAL_SYNC_METADATA_VERSION,
330
+ backend: config.backend,
331
+ account,
332
+ vault: config.vault,
333
+ item: itemTitle(config, account),
334
+ remoteAuthJsonSha256: remoteMetadata?.authJsonSha256 ?? hash,
335
+ lastSyncedAuthJsonSha256: hash,
336
+ lastSyncedAt: new Date().toISOString(),
337
+ deviceId: getLocalDeviceId(),
338
+ };
339
+ await writeFilePrivate(getLocalSyncMetadataPath(paths, account), `${JSON.stringify(metadata, null, 2)}\n`);
340
+ }
341
+
214
342
  function remotePaths(options: RemotePathOptions & { readonly env?: NodeJS.ProcessEnv } = {}): CodexPaths {
215
343
  return options.paths ?? getCodexPaths(options.env ?? process.env);
216
344
  }
@@ -542,9 +670,11 @@ function authFieldAssignment(authJson: string): string {
542
670
  async function upsertOnePasswordAuthJson(
543
671
  config: RemoteConfig,
544
672
  item: string,
673
+ account: string,
545
674
  authJson: string,
546
675
  env: NodeJS.ProcessEnv,
547
- ): Promise<'created' | 'updated'> {
676
+ ): Promise<{ operation: 'created' | 'updated'; metadata: RemoteAuthMetadata }> {
677
+ const metadata = buildRemoteAuthMetadata(account, authJson);
548
678
  if (await onePasswordItemExists(config, item, env)) {
549
679
  await runOp([
550
680
  'item',
@@ -553,8 +683,9 @@ async function upsertOnePasswordAuthJson(
553
683
  '--vault',
554
684
  config.vault,
555
685
  authFieldAssignment(authJson),
686
+ metadataAssignment(metadata),
556
687
  ], env, `updating 1Password item '${item}'`, { sensitive: true });
557
- return 'updated';
688
+ return { operation: 'updated', metadata };
558
689
  }
559
690
 
560
691
  await runOp([
@@ -567,8 +698,9 @@ async function upsertOnePasswordAuthJson(
567
698
  '--title',
568
699
  item,
569
700
  authFieldAssignment(authJson),
701
+ metadataAssignment(metadata),
570
702
  ], env, `creating 1Password item '${item}'`, { sensitive: true });
571
- return 'created';
703
+ return { operation: 'created', metadata };
572
704
  }
573
705
 
574
706
  function stripOneTrailingLineEnding(value: string): string {
@@ -600,7 +732,36 @@ function decodeAuthJsonField(stdout: string, item: string): string {
600
732
  }
601
733
  }
602
734
 
735
+ function parseRemoteAuthMetadata(value: unknown): RemoteAuthMetadata | null {
736
+ if (typeof value !== 'string' || value.trim().length === 0) {
737
+ return null;
738
+ }
739
+ try {
740
+ const parsed = JSON.parse(value) as unknown;
741
+ if (!isRecord(parsed)
742
+ || parsed.version !== 1
743
+ || typeof parsed.account !== 'string'
744
+ || typeof parsed.authJsonSha256 !== 'string'
745
+ || typeof parsed.updatedAt !== 'string') {
746
+ return null;
747
+ }
748
+ return {
749
+ version: 1,
750
+ account: parsed.account,
751
+ authJsonSha256: parsed.authJsonSha256,
752
+ updatedAt: parsed.updatedAt,
753
+ ...(typeof parsed.deviceId === 'string' ? { deviceId: parsed.deviceId } : {}),
754
+ };
755
+ } catch {
756
+ return null;
757
+ }
758
+ }
759
+
603
760
  function decodeAuthJsonFieldFromItemJson(stdout: string, item: string): string {
761
+ return decodeOnePasswordAccountFromItemJson(stdout, item).authJson;
762
+ }
763
+
764
+ function decodeOnePasswordAccountFromItemJson(stdout: string, item: string): { authJson: string; metadata: RemoteAuthMetadata | null } {
604
765
  let parsed: unknown;
605
766
  try {
606
767
  parsed = JSON.parse(stdout) as unknown;
@@ -623,7 +784,14 @@ function decodeAuthJsonFieldFromItemJson(stdout: string, item: string): string {
623
784
  }
624
785
 
625
786
  parseAuthJsonString(field.value, `auth_json field in 1Password item '${item}'`);
626
- return field.value;
787
+
788
+ const metadataField = parsed.fields.find((candidate: unknown): candidate is Record<string, unknown> => (
789
+ isRecord(candidate) && candidate.label === REMOTE_METADATA_FIELD
790
+ ));
791
+ return {
792
+ authJson: field.value,
793
+ metadata: parseRemoteAuthMetadata(metadataField?.value),
794
+ };
627
795
  }
628
796
 
629
797
  async function readOnePasswordAuthJson(
@@ -631,6 +799,14 @@ async function readOnePasswordAuthJson(
631
799
  item: string,
632
800
  env: NodeJS.ProcessEnv,
633
801
  ): Promise<string> {
802
+ return (await readOnePasswordAccount(config, item, env)).authJson;
803
+ }
804
+
805
+ async function readOnePasswordAccount(
806
+ config: RemoteConfig,
807
+ item: string,
808
+ env: NodeJS.ProcessEnv,
809
+ ): Promise<{ authJson: string; metadata: RemoteAuthMetadata | null }> {
634
810
  const jsonResult = await runOpRaw([
635
811
  'item',
636
812
  'get',
@@ -643,7 +819,7 @@ async function readOnePasswordAuthJson(
643
819
 
644
820
  if (jsonResult.exitCode === 0) {
645
821
  try {
646
- return decodeAuthJsonFieldFromItemJson(jsonResult.stdout, item);
822
+ return decodeOnePasswordAccountFromItemJson(jsonResult.stdout, item);
647
823
  } catch (error) {
648
824
  if (!errorMessage(error).includes(`'${ONEPASSWORD_AUTH_FIELD}'`)) {
649
825
  throw error;
@@ -669,7 +845,7 @@ async function readOnePasswordAuthJson(
669
845
  ], env);
670
846
 
671
847
  if (fieldResult.exitCode === 0) {
672
- return decodeAuthJsonField(fieldResult.stdout, item);
848
+ return { authJson: decodeAuthJsonField(fieldResult.stdout, item), metadata: null };
673
849
  }
674
850
  if (looksLikeMissingField(fieldResult)) {
675
851
  throw new CxError(`1Password item '${item}' does not contain a revealable '${ONEPASSWORD_AUTH_FIELD}' field`, 1);
@@ -740,7 +916,8 @@ export async function syncPushAccount(
740
916
  await writebackCurrentAccountIfSyncTarget(safeAccount, paths);
741
917
  const local = await readLocalAccountAuthJson(safeAccount, paths);
742
918
  const item = itemTitle(config, local.account);
743
- const operation = await upsertOnePasswordAuthJson(config, item, local.authJson, env);
919
+ const upload = await upsertOnePasswordAuthJson(config, item, local.account, local.authJson, env);
920
+ await writeLocalSyncMetadata(paths, config, local.account, local.authJson, upload.metadata);
744
921
 
745
922
  return {
746
923
  account: local.account,
@@ -748,7 +925,7 @@ export async function syncPushAccount(
748
925
  backend: config.backend,
749
926
  vault: config.vault,
750
927
  item,
751
- operation,
928
+ operation: upload.operation,
752
929
  };
753
930
  }
754
931
 
@@ -761,8 +938,14 @@ export async function syncPullAccount(
761
938
  const config = await requireRemoteConfig({ paths });
762
939
  const safeAccount = validateRemoteSyncAccountName(account);
763
940
  const item = itemTitle(config, safeAccount);
764
- const authJson = await readOnePasswordAuthJson(config, item, env);
765
- const local = await writeLocalAccountAuthJson(safeAccount, authJson, paths, options.force === true);
941
+ const remote = await readOnePasswordAccount(config, item, env);
942
+ const verifiedMetadata = remote.metadata
943
+ && remote.metadata.account === safeAccount
944
+ && remote.metadata.authJsonSha256 === sha256Hex(remote.authJson)
945
+ ? remote.metadata
946
+ : null;
947
+ const local = await writeLocalAccountAuthJson(safeAccount, remote.authJson, paths, options.force === true);
948
+ await writeLocalSyncMetadata(paths, config, safeAccount, remote.authJson, verifiedMetadata);
766
949
 
767
950
  return {
768
951
  account: local.account,
@@ -805,6 +988,199 @@ export async function syncPushAllAccounts(options: RemoteCliOptions = {}): Promi
805
988
  return results;
806
989
  }
807
990
 
991
+ function autoSyncDisabled(env: NodeJS.ProcessEnv): boolean {
992
+ return env.CX_AUTO_SYNC === '0' || env.CX_MAGIC_SYNC === '0' || env.CX_NO_MAGIC_SYNC === '1';
993
+ }
994
+
995
+ async function readLocalAuthHash(account: string, paths: CodexPaths): Promise<string | null> {
996
+ try {
997
+ const raw = await readFile(accountPathForName(paths, account), 'utf8');
998
+ parseAuthJsonString(raw, `Codex account '${account}'`);
999
+ return sha256Hex(raw);
1000
+ } catch (error) {
1001
+ if (isNotFoundError(error)) {
1002
+ return null;
1003
+ }
1004
+ throw error;
1005
+ }
1006
+ }
1007
+
1008
+ async function readRemoteAccountMetadata(
1009
+ account: string,
1010
+ config: RemoteConfig,
1011
+ env: NodeJS.ProcessEnv,
1012
+ ): Promise<{ metadata: RemoteAuthMetadata | null; authJson?: string }> {
1013
+ const item = itemTitle(config, account);
1014
+ const remote = await readOnePasswordAccount(config, item, env);
1015
+ const actualHash = sha256Hex(remote.authJson);
1016
+ const metadata = remote.metadata
1017
+ && remote.metadata.account === account
1018
+ && remote.metadata.authJsonSha256 === actualHash
1019
+ ? remote.metadata
1020
+ : null;
1021
+ return { metadata, authJson: remote.authJson };
1022
+ }
1023
+
1024
+ function classifyHashes(
1025
+ localHash: string | null,
1026
+ localMetadata: LocalSyncMetadata | null,
1027
+ remoteMetadata: RemoteAuthMetadata | null,
1028
+ ): SyncState {
1029
+ if (!remoteMetadata || !localHash || !localMetadata) {
1030
+ return 'unknown';
1031
+ }
1032
+ if (localHash === remoteMetadata.authJsonSha256) {
1033
+ return 'in-sync';
1034
+ }
1035
+ const localChangedSinceSync = localHash !== localMetadata.lastSyncedAuthJsonSha256;
1036
+ const remoteChangedSinceSync = remoteMetadata.authJsonSha256 !== localMetadata.remoteAuthJsonSha256
1037
+ || remoteMetadata.authJsonSha256 !== localMetadata.lastSyncedAuthJsonSha256;
1038
+ if (!localChangedSinceSync && remoteChangedSinceSync) {
1039
+ return 'remote-newer';
1040
+ }
1041
+ if (localChangedSinceSync && !remoteChangedSinceSync) {
1042
+ return 'local-newer';
1043
+ }
1044
+ if (localChangedSinceSync && remoteChangedSinceSync) {
1045
+ return 'diverged';
1046
+ }
1047
+ return 'unknown';
1048
+ }
1049
+
1050
+ async function inspectAccountSyncState(
1051
+ account: string,
1052
+ config: RemoteConfig | null,
1053
+ env: NodeJS.ProcessEnv,
1054
+ paths: CodexPaths,
1055
+ ): Promise<{ state: SyncState; error: string | null }> {
1056
+ if (!config) {
1057
+ return { state: 'unknown', error: 'remote backend is not configured' };
1058
+ }
1059
+ try {
1060
+ const [localHash, localMetadata, remote] = await Promise.all([
1061
+ readLocalAuthHash(account, paths),
1062
+ readLocalSyncMetadata(paths, account),
1063
+ readRemoteAccountMetadata(account, config, env),
1064
+ ]);
1065
+ return { state: classifyHashes(localHash, localMetadata, remote.metadata), error: null };
1066
+ } catch (error) {
1067
+ return { state: 'unknown', error: errorMessage(error) };
1068
+ }
1069
+ }
1070
+
1071
+ export async function autoPullAccountForUse(
1072
+ account: string,
1073
+ options: RemoteCliOptions = {},
1074
+ ): Promise<AutoSyncResult> {
1075
+ const env = options.env ?? process.env;
1076
+ const paths = remotePaths(options);
1077
+ const safeAccount = validateAccountName(account);
1078
+ if (safeAccount === 'default') {
1079
+ return { action: 'skipped', account: safeAccount, reason: 'reserved default account' };
1080
+ }
1081
+ if (autoSyncDisabled(env)) {
1082
+ return { action: 'skipped', account: safeAccount, reason: 'auto sync disabled' };
1083
+ }
1084
+ const config = await readRemoteConfig({ paths });
1085
+ if (!config) {
1086
+ return { action: 'skipped', account: safeAccount, reason: 'remote backend is not configured' };
1087
+ }
1088
+
1089
+ const item = itemTitle(config, safeAccount);
1090
+ const accountFile = accountPathForName(paths, safeAccount);
1091
+ const localExists = await pathExists(accountFile);
1092
+ if (!localExists) {
1093
+ const pulled = await syncPullAccount(safeAccount, { env, paths });
1094
+ return { action: 'pulled', account: pulled.account, item: pulled.item, backend: pulled.backend, reason: 'local account missing' };
1095
+ }
1096
+
1097
+ const localHash = await readLocalAuthHash(safeAccount, paths);
1098
+ const localMetadata = await readLocalSyncMetadata(paths, safeAccount);
1099
+ const remote = await readRemoteAccountMetadata(safeAccount, config, env).catch((error: unknown) => ({
1100
+ metadata: null,
1101
+ error: errorMessage(error),
1102
+ }));
1103
+ if ('error' in remote) {
1104
+ return { action: 'skipped', account: safeAccount, item, backend: config.backend, reason: `remote unavailable: ${remote.error}` };
1105
+ }
1106
+ const state = classifyHashes(localHash, localMetadata, remote.metadata);
1107
+ if (state === 'remote-newer') {
1108
+ const pulled = await syncPullAccount(safeAccount, { env, paths, force: true });
1109
+ return { action: 'pulled', account: pulled.account, item: pulled.item, backend: pulled.backend, reason: 'remote newer' };
1110
+ }
1111
+ if (state === 'diverged') {
1112
+ throw new CxError(`sync conflict for '${safeAccount}': local and remote credentials diverged; use 'cx sync status ${safeAccount}', then resolve with explicit 'cx sync pull ${safeAccount} --force' or 'cx sync push ${safeAccount}'`, 1);
1113
+ }
1114
+ return { action: 'skipped', account: safeAccount, item, backend: config.backend, reason: state };
1115
+ }
1116
+
1117
+ export async function autoPushAccountIfChanged(
1118
+ account: string,
1119
+ options: RemoteCliOptions = {},
1120
+ ): Promise<AutoSyncResult> {
1121
+ const env = options.env ?? process.env;
1122
+ const paths = remotePaths(options);
1123
+ const safeAccount = validateAccountName(account);
1124
+ if (safeAccount === 'default') {
1125
+ return { action: 'skipped', account: safeAccount, reason: 'reserved default account' };
1126
+ }
1127
+ if (autoSyncDisabled(env)) {
1128
+ return { action: 'skipped', account: safeAccount, reason: 'auto sync disabled' };
1129
+ }
1130
+ const config = await readRemoteConfig({ paths });
1131
+ if (!config) {
1132
+ return { action: 'skipped', account: safeAccount, reason: 'remote backend is not configured' };
1133
+ }
1134
+ const localHash = await readLocalAuthHash(safeAccount, paths);
1135
+ if (!localHash) {
1136
+ return { action: 'skipped', account: safeAccount, reason: 'local account missing' };
1137
+ }
1138
+ const localMetadata = await readLocalSyncMetadata(paths, safeAccount);
1139
+ const remote = await readRemoteAccountMetadata(safeAccount, config, env).then(
1140
+ (value) => ({ ...value, missing: false, unavailable: null as string | null }),
1141
+ (error: unknown) => {
1142
+ const message = errorMessage(error);
1143
+ if (message.includes('was not found')) {
1144
+ return { metadata: null, missing: true, unavailable: null as string | null };
1145
+ }
1146
+ return { metadata: null, missing: false, unavailable: message };
1147
+ },
1148
+ );
1149
+ if (remote.unavailable) {
1150
+ return { action: 'skipped', account: safeAccount, item: itemTitle(config, safeAccount), backend: config.backend, reason: `remote unavailable: ${remote.unavailable}` };
1151
+ }
1152
+ if (remote.missing) {
1153
+ if (localMetadata) {
1154
+ return { action: 'skipped', account: safeAccount, item: itemTitle(config, safeAccount), backend: config.backend, reason: 'remote missing after previous sync' };
1155
+ }
1156
+ const pushed = await syncPushAccount(safeAccount, { env, paths });
1157
+ return { action: 'pushed', account: pushed.account, item: pushed.item, backend: pushed.backend, reason: 'remote missing' };
1158
+ }
1159
+ const state = classifyHashes(localHash, localMetadata, remote.metadata);
1160
+ if (state === 'in-sync') {
1161
+ return { action: 'skipped', account: safeAccount, item: itemTitle(config, safeAccount), backend: config.backend, reason: 'in-sync' };
1162
+ }
1163
+ if (state === 'diverged') {
1164
+ throw new CxError(`sync conflict for '${safeAccount}': local and remote credentials diverged; use 'cx sync status ${safeAccount}', then resolve with explicit 'cx sync pull ${safeAccount} --force' or 'cx sync push ${safeAccount}'`, 1);
1165
+ }
1166
+ if (state !== 'local-newer') {
1167
+ return { action: 'skipped', account: safeAccount, item: itemTitle(config, safeAccount), backend: config.backend, reason: state };
1168
+ }
1169
+ const pushed = await syncPushAccount(safeAccount, { env, paths });
1170
+ return { action: 'pushed', account: pushed.account, item: pushed.item, backend: pushed.backend, reason: state };
1171
+ }
1172
+
1173
+ export async function writebackAndAutoPushCurrentAccount(
1174
+ options: RemoteCliOptions = {},
1175
+ ): Promise<AutoSyncResult | null> {
1176
+ const paths = remotePaths(options);
1177
+ const writeback = await writebackCurrentAccount({ paths });
1178
+ if (!writeback.performed || !writeback.account) {
1179
+ return null;
1180
+ }
1181
+ return await autoPushAccountIfChanged(writeback.account, options);
1182
+ }
1183
+
808
1184
  export async function setupOnePasswordProfiles(
809
1185
  input: SetupOnePasswordProfilesInput,
810
1186
  options: RemoteCliOptions = {},
@@ -878,6 +1254,8 @@ export async function inspectSyncStatus(
878
1254
  }
879
1255
  }
880
1256
 
1257
+ const sync = await inspectAccountSyncState(accountName, config, env, paths);
1258
+
881
1259
  statuses.push({
882
1260
  account: accountName,
883
1261
  item: remoteItem,
@@ -889,6 +1267,7 @@ export async function inspectSyncStatus(
889
1267
  presence,
890
1268
  error: remoteError,
891
1269
  },
1270
+ sync,
892
1271
  });
893
1272
  }
894
1273