@ralphkrauss/codex-account-switcher 0.1.5 → 0.1.6
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/CHANGELOG.md +13 -0
- package/README.md +39 -18
- package/dist/cli.js +166 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/remote.d.ts +19 -0
- package/dist/remote.js +121 -2
- package/dist/remote.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +187 -12
- package/src/index.ts +6 -0
- package/src/remote.ts +156 -1
package/src/remote.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
getCodexPaths,
|
|
18
18
|
listAccountNames,
|
|
19
19
|
resolveExecutable,
|
|
20
|
+
useAccount,
|
|
20
21
|
validateAccountName,
|
|
21
22
|
writebackCurrentAccount,
|
|
22
23
|
type CodexPaths,
|
|
@@ -113,6 +114,23 @@ export interface SyncStatus {
|
|
|
113
114
|
readonly accounts: readonly SyncStatusAccount[];
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
export interface SetupOnePasswordProfilesInput extends ConfigureOnePasswordRemoteInput {
|
|
118
|
+
readonly pull?: boolean;
|
|
119
|
+
readonly force?: boolean;
|
|
120
|
+
readonly use?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface SetupOnePasswordProfilesResult {
|
|
124
|
+
readonly configPath: string;
|
|
125
|
+
readonly remoteConfigured: boolean;
|
|
126
|
+
readonly opAvailable: boolean;
|
|
127
|
+
readonly vault: string;
|
|
128
|
+
readonly itemPrefix: string;
|
|
129
|
+
readonly remoteAccounts: readonly string[];
|
|
130
|
+
readonly pulledAccounts: readonly string[];
|
|
131
|
+
readonly usedAccount: string | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
116
134
|
interface OpResult {
|
|
117
135
|
readonly exitCode: number;
|
|
118
136
|
readonly stdout: string;
|
|
@@ -415,6 +433,68 @@ function itemTitle(config: RemoteConfig, account: string): string {
|
|
|
415
433
|
return `${config.itemPrefix}${account}`;
|
|
416
434
|
}
|
|
417
435
|
|
|
436
|
+
function accountNameFromItemTitle(config: RemoteConfig, title: string): string | null {
|
|
437
|
+
if (!title.startsWith(config.itemPrefix)) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const account = title.slice(config.itemPrefix.length);
|
|
441
|
+
try {
|
|
442
|
+
const safeAccount = validateAccountName(account);
|
|
443
|
+
return safeAccount === 'default' ? null : safeAccount;
|
|
444
|
+
} catch {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function sortedUnique(values: Iterable<string>): string[] {
|
|
450
|
+
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function parseOnePasswordItemTitles(stdout: string, action: string): string[] {
|
|
454
|
+
let parsed: unknown;
|
|
455
|
+
try {
|
|
456
|
+
parsed = JSON.parse(stdout) as unknown;
|
|
457
|
+
} catch (error) {
|
|
458
|
+
throw new CxError(`1Password item list JSON could not be parsed while ${action}: ${errorMessage(error)}`, 1);
|
|
459
|
+
}
|
|
460
|
+
if (!Array.isArray(parsed)) {
|
|
461
|
+
throw new CxError(`1Password item list did not return an array while ${action}`, 1);
|
|
462
|
+
}
|
|
463
|
+
return parsed
|
|
464
|
+
.map((item) => (isRecord(item) && typeof item.title === 'string' ? item.title : null))
|
|
465
|
+
.filter((title): title is string => title !== null);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function listOnePasswordAccountNames(
|
|
469
|
+
config: RemoteConfig,
|
|
470
|
+
env: NodeJS.ProcessEnv,
|
|
471
|
+
): Promise<string[]> {
|
|
472
|
+
const result = await runOp([
|
|
473
|
+
'item',
|
|
474
|
+
'list',
|
|
475
|
+
'--vault',
|
|
476
|
+
config.vault,
|
|
477
|
+
'--format',
|
|
478
|
+
'json',
|
|
479
|
+
], env, `listing 1Password items in vault '${config.vault}'`);
|
|
480
|
+
|
|
481
|
+
return sortedUnique(
|
|
482
|
+
parseOnePasswordItemTitles(result.stdout, `listing 1Password items in vault '${config.vault}'`)
|
|
483
|
+
.map((title) => accountNameFromItemTitle(config, title))
|
|
484
|
+
.filter((account): account is string => account !== null),
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function verifyOnePasswordVault(config: RemoteConfig, env: NodeJS.ProcessEnv): Promise<void> {
|
|
489
|
+
await runOp([
|
|
490
|
+
'vault',
|
|
491
|
+
'get',
|
|
492
|
+
config.vault,
|
|
493
|
+
'--format',
|
|
494
|
+
'json',
|
|
495
|
+
], env, `checking 1Password vault '${config.vault}'`);
|
|
496
|
+
}
|
|
497
|
+
|
|
418
498
|
function validateRemoteSyncAccountName(account: string): string {
|
|
419
499
|
const safeAccount = validateAccountName(account);
|
|
420
500
|
if (safeAccount === 'default') {
|
|
@@ -685,6 +765,73 @@ export async function syncPullAccount(
|
|
|
685
765
|
};
|
|
686
766
|
}
|
|
687
767
|
|
|
768
|
+
export async function listRemoteAccountNames(options: RemoteCliOptions = {}): Promise<string[]> {
|
|
769
|
+
const env = options.env ?? process.env;
|
|
770
|
+
const paths = remotePaths(options);
|
|
771
|
+
const config = await requireRemoteConfig({ paths });
|
|
772
|
+
return await listOnePasswordAccountNames(config, env);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
export async function syncPullAllAccounts(options: RemoteForceOptions = {}): Promise<SyncPullResult[]> {
|
|
776
|
+
const paths = remotePaths(options);
|
|
777
|
+
const accounts = await listRemoteAccountNames(options);
|
|
778
|
+
const results: SyncPullResult[] = [];
|
|
779
|
+
for (const account of accounts) {
|
|
780
|
+
const accountFile = accountPathForName(paths, account);
|
|
781
|
+
if (options.force !== true && await pathExists(accountFile)) {
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
results.push(await syncPullAccount(account, options));
|
|
785
|
+
}
|
|
786
|
+
return results;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
export async function syncPushAllAccounts(options: RemoteCliOptions = {}): Promise<SyncPushResult[]> {
|
|
790
|
+
const paths = remotePaths(options);
|
|
791
|
+
const accounts = await listRemoteSyncAccountNames(paths);
|
|
792
|
+
const results: SyncPushResult[] = [];
|
|
793
|
+
for (const account of accounts) {
|
|
794
|
+
results.push(await syncPushAccount(account, options));
|
|
795
|
+
}
|
|
796
|
+
return results;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export async function setupOnePasswordProfiles(
|
|
800
|
+
input: SetupOnePasswordProfilesInput,
|
|
801
|
+
options: RemoteCliOptions = {},
|
|
802
|
+
): Promise<SetupOnePasswordProfilesResult> {
|
|
803
|
+
const env = options.env ?? process.env;
|
|
804
|
+
const paths = remotePaths(options);
|
|
805
|
+
const configured = await configureOnePasswordRemote(input, { paths });
|
|
806
|
+
await verifyOnePasswordVault(configured.config, env);
|
|
807
|
+
const remoteAccounts = await listOnePasswordAccountNames(configured.config, env);
|
|
808
|
+
const pulled = input.pull === true
|
|
809
|
+
? await syncPullAllAccounts({ paths, env, force: input.force })
|
|
810
|
+
: [];
|
|
811
|
+
let usedAccount: string | null = null;
|
|
812
|
+
|
|
813
|
+
if (input.use) {
|
|
814
|
+
const account = validateRemoteSyncAccountName(input.use);
|
|
815
|
+
const accountFile = accountPathForName(paths, account);
|
|
816
|
+
if (!await pathExists(accountFile)) {
|
|
817
|
+
await syncPullAccount(account, { paths, env, force: input.force });
|
|
818
|
+
}
|
|
819
|
+
await useAccount(account, { paths });
|
|
820
|
+
usedAccount = account;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return {
|
|
824
|
+
configPath: configured.configPath,
|
|
825
|
+
remoteConfigured: true,
|
|
826
|
+
opAvailable: true,
|
|
827
|
+
vault: configured.config.vault,
|
|
828
|
+
itemPrefix: configured.config.itemPrefix,
|
|
829
|
+
remoteAccounts,
|
|
830
|
+
pulledAccounts: pulled.map((entry) => entry.account),
|
|
831
|
+
usedAccount,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
688
835
|
export async function inspectSyncStatus(
|
|
689
836
|
account?: string,
|
|
690
837
|
options: RemoteCliOptions = {},
|
|
@@ -693,9 +840,17 @@ export async function inspectSyncStatus(
|
|
|
693
840
|
const paths = remotePaths(options);
|
|
694
841
|
const config = await readRemoteConfig({ paths });
|
|
695
842
|
const opPath = await resolveExecutable('op', env);
|
|
696
|
-
|
|
843
|
+
let accounts = account ? [validateRemoteSyncAccountName(account)] : await listRemoteSyncAccountNames(paths);
|
|
697
844
|
const statuses: SyncStatusAccount[] = [];
|
|
698
845
|
|
|
846
|
+
if (!account && config && opPath) {
|
|
847
|
+
try {
|
|
848
|
+
accounts = sortedUnique([...accounts, ...await listOnePasswordAccountNames(config, env)]);
|
|
849
|
+
} catch {
|
|
850
|
+
// Keep local status useful; per-account remote errors are reported below.
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
699
854
|
for (const accountName of accounts) {
|
|
700
855
|
const accountFile = accountPathForName(paths, accountName);
|
|
701
856
|
let presence: RemotePresence = 'unknown';
|