@ralphkrauss/codex-account-switcher 0.1.5 → 0.1.7
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 +20 -0
- package/README.md +63 -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/docs/AGENT_SETUP.md +317 -0
- package/package.json +6 -2
- package/src/cli.ts +187 -12
- package/src/index.ts +6 -0
- package/src/remote.ts +156 -1
package/src/cli.ts
CHANGED
|
@@ -17,9 +17,12 @@ import {
|
|
|
17
17
|
renameAccount,
|
|
18
18
|
runCodex,
|
|
19
19
|
saveAccount,
|
|
20
|
+
setupOnePasswordProfiles,
|
|
20
21
|
syncHermesAccount,
|
|
21
22
|
syncPullAccount,
|
|
23
|
+
syncPullAllAccounts,
|
|
22
24
|
syncPushAccount,
|
|
25
|
+
syncPushAllAccounts,
|
|
23
26
|
useAccount,
|
|
24
27
|
useHermesAccount,
|
|
25
28
|
validateAccountName,
|
|
@@ -32,6 +35,7 @@ import {
|
|
|
32
35
|
|
|
33
36
|
const PACKAGE_NAME = '@ralphkrauss/codex-account-switcher';
|
|
34
37
|
const SUBCOMMANDS = new Set([
|
|
38
|
+
'1password',
|
|
35
39
|
'doctor',
|
|
36
40
|
'hermes',
|
|
37
41
|
'help',
|
|
@@ -79,6 +83,12 @@ interface ParsedRemoteConfigureArgs {
|
|
|
79
83
|
readonly itemPrefix?: string;
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
interface ParsedOnePasswordSetupArgs extends ParsedRemoteConfigureArgs {
|
|
87
|
+
readonly pull: boolean;
|
|
88
|
+
readonly force: boolean;
|
|
89
|
+
readonly use?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
82
92
|
function write(stream: NodeJS.WritableStream, text: string): void {
|
|
83
93
|
stream.write(text.endsWith('\n') ? text : `${text}\n`);
|
|
84
94
|
}
|
|
@@ -112,10 +122,12 @@ Usage:
|
|
|
112
122
|
cx hermes use <account> [--profile <name>] [--no-config]
|
|
113
123
|
cx hermes sync <account> [--profile <name>]
|
|
114
124
|
cx hermes status [--profile <name>] [--json]
|
|
125
|
+
cx 1password setup --vault <vault> [--item-prefix <prefix>] [--pull] [--force] [--use <account>]
|
|
126
|
+
cx 1password status [--json]
|
|
115
127
|
cx remote configure 1password --vault <vault> [--item-prefix <prefix>]
|
|
116
128
|
cx remote status [--json]
|
|
117
|
-
cx sync push <account
|
|
118
|
-
cx sync pull <account
|
|
129
|
+
cx sync push <account>|--all
|
|
130
|
+
cx sync pull <account>|--all [--force]
|
|
119
131
|
cx sync status [account] [--json]
|
|
120
132
|
cx doctor [--json]
|
|
121
133
|
cx --help
|
|
@@ -164,15 +176,32 @@ Notes:
|
|
|
164
176
|
The default 1Password item prefix is cx-. Token contents are never printed.`;
|
|
165
177
|
}
|
|
166
178
|
|
|
179
|
+
function onePasswordHelpText(): string {
|
|
180
|
+
return `Usage:
|
|
181
|
+
cx 1password setup --vault <vault> [--item-prefix <prefix>] [--pull] [--force] [--use <account>]
|
|
182
|
+
cx 1password status [--json]
|
|
183
|
+
|
|
184
|
+
Commands:
|
|
185
|
+
setup Configure 1Password as the native profile backend, verify op/vault access,
|
|
186
|
+
optionally pull all remote profiles, and optionally select one profile.
|
|
187
|
+
status Show 1Password backend status and local/remote profile presence.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
cx 1password setup --vault Private --pull --use gi
|
|
191
|
+
cx 1password setup --vault Private --item-prefix codex-`;
|
|
192
|
+
}
|
|
193
|
+
|
|
167
194
|
function syncHelpText(): string {
|
|
168
195
|
return `Usage:
|
|
169
|
-
cx sync push <account
|
|
170
|
-
cx sync pull <account
|
|
196
|
+
cx sync push <account>|--all
|
|
197
|
+
cx sync pull <account>|--all [--force]
|
|
171
198
|
cx sync status [account] [--json]
|
|
172
199
|
|
|
173
200
|
Commands:
|
|
174
201
|
push Upsert CODEX_HOME/accounts/<account>.json into the configured 1Password item.
|
|
202
|
+
With --all, push every local named account except reserved default.
|
|
175
203
|
pull Read the configured 1Password item into CODEX_HOME/accounts/<account>.json.
|
|
204
|
+
With --all, pull every remote 1Password-backed profile not already local.
|
|
176
205
|
Refuses to overwrite unless --force is passed.
|
|
177
206
|
status Compare local account-file presence with remote item presence without printing tokens.`;
|
|
178
207
|
}
|
|
@@ -277,6 +306,62 @@ function parseRemoteConfigureArgs(args: readonly string[]): ParsedRemoteConfigur
|
|
|
277
306
|
return { vault, ...(itemPrefix ? { itemPrefix } : {}) };
|
|
278
307
|
}
|
|
279
308
|
|
|
309
|
+
function parseOnePasswordSetupArgs(args: readonly string[]): ParsedOnePasswordSetupArgs {
|
|
310
|
+
let vault: string | undefined;
|
|
311
|
+
let itemPrefix: string | undefined;
|
|
312
|
+
let pull = false;
|
|
313
|
+
let force = false;
|
|
314
|
+
let use: string | undefined;
|
|
315
|
+
|
|
316
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
317
|
+
const arg = args[index] ?? '';
|
|
318
|
+
if (arg === '--vault') {
|
|
319
|
+
const value = args[index + 1];
|
|
320
|
+
if (!value || value.startsWith('-')) {
|
|
321
|
+
throw new CxError('usage: cx 1password setup --vault <vault> [--item-prefix <prefix>] [--pull] [--force] [--use <account>]', 2);
|
|
322
|
+
}
|
|
323
|
+
vault = value;
|
|
324
|
+
index += 1;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (arg === '--item-prefix') {
|
|
328
|
+
const value = args[index + 1];
|
|
329
|
+
if (!value || value.startsWith('-')) {
|
|
330
|
+
throw new CxError('usage: cx 1password setup --vault <vault> [--item-prefix <prefix>] [--pull] [--force] [--use <account>]', 2);
|
|
331
|
+
}
|
|
332
|
+
itemPrefix = value;
|
|
333
|
+
index += 1;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (arg === '--pull') {
|
|
337
|
+
pull = true;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (arg === '--force') {
|
|
341
|
+
force = true;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (arg === '--use') {
|
|
345
|
+
const value = args[index + 1];
|
|
346
|
+
if (!value || value.startsWith('-')) {
|
|
347
|
+
throw new CxError('usage: cx 1password setup --vault <vault> [--item-prefix <prefix>] [--pull] [--force] [--use <account>]', 2);
|
|
348
|
+
}
|
|
349
|
+
use = value;
|
|
350
|
+
index += 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (arg.startsWith('--')) {
|
|
354
|
+
throw new CxError(`unknown option '${arg}'`, 2);
|
|
355
|
+
}
|
|
356
|
+
throw new CxError('usage: cx 1password setup --vault <vault> [--item-prefix <prefix>] [--pull] [--force] [--use <account>]', 2);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!vault) {
|
|
360
|
+
throw new CxError('usage: cx 1password setup --vault <vault> [--item-prefix <prefix>] [--pull] [--force] [--use <account>]', 2);
|
|
361
|
+
}
|
|
362
|
+
return { vault, pull, force, ...(itemPrefix ? { itemPrefix } : {}), ...(use ? { use } : {}) };
|
|
363
|
+
}
|
|
364
|
+
|
|
280
365
|
function parseLoginArgs(args: readonly string[]): ParsedLoginArgs {
|
|
281
366
|
let force = false;
|
|
282
367
|
let name: string | null = null;
|
|
@@ -473,6 +558,26 @@ async function printList(io: CliIo, env: NodeJS.ProcessEnv): Promise<void> {
|
|
|
473
558
|
write(io.stdout, formatAccounts(await listAccounts(getCodexPaths(env))));
|
|
474
559
|
}
|
|
475
560
|
|
|
561
|
+
async function useAccountWithRemoteFallback(name: string, env: NodeJS.ProcessEnv, io: CliIo): Promise<void> {
|
|
562
|
+
const paths = getCodexPaths(env);
|
|
563
|
+
try {
|
|
564
|
+
await useAccount(name, { paths });
|
|
565
|
+
return;
|
|
566
|
+
} catch (error) {
|
|
567
|
+
if (!(error instanceof CxError) || !error.message.includes(`no account '${name}'`)) {
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
const pulled = await syncPullAccount(name, { env, paths });
|
|
573
|
+
write(io.stdout, `pulled 1Password-backed profile '${pulled.account}'`);
|
|
574
|
+
await useAccount(name, { paths });
|
|
575
|
+
} catch {
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
476
581
|
function parseRunArgs(args: readonly string[]): { account: string | null; codexArgs: readonly string[] } {
|
|
477
582
|
const separatorIndex = args.indexOf('--');
|
|
478
583
|
if (separatorIndex >= 0) {
|
|
@@ -591,6 +696,48 @@ async function handleRemoteCommand(
|
|
|
591
696
|
}
|
|
592
697
|
}
|
|
593
698
|
|
|
699
|
+
async function handleOnePasswordCommand(
|
|
700
|
+
args: readonly string[],
|
|
701
|
+
env: NodeJS.ProcessEnv,
|
|
702
|
+
io: CliIo,
|
|
703
|
+
): Promise<number> {
|
|
704
|
+
const [command, ...rest] = args;
|
|
705
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
706
|
+
write(io.stdout, onePasswordHelpText());
|
|
707
|
+
return 0;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
switch (command) {
|
|
711
|
+
case 'setup': {
|
|
712
|
+
const parsed = parseOnePasswordSetupArgs(rest);
|
|
713
|
+
const result = await setupOnePasswordProfiles(parsed, { env, paths: getCodexPaths(env) });
|
|
714
|
+
write(io.stdout, 'configured 1Password-backed Codex profiles');
|
|
715
|
+
write(io.stdout, `config: ${result.configPath}`);
|
|
716
|
+
write(io.stdout, `vault: ${result.vault}`);
|
|
717
|
+
write(io.stdout, `item prefix: ${result.itemPrefix}`);
|
|
718
|
+
write(io.stdout, `remote profiles: ${result.remoteAccounts.length > 0 ? result.remoteAccounts.join(', ') : '(none)'}`);
|
|
719
|
+
if (parsed.pull) {
|
|
720
|
+
write(io.stdout, `pulled profiles: ${result.pulledAccounts.length > 0 ? result.pulledAccounts.join(', ') : '(none)'}`);
|
|
721
|
+
}
|
|
722
|
+
if (result.usedAccount) {
|
|
723
|
+
write(io.stdout, `active codex account: ${result.usedAccount}`);
|
|
724
|
+
}
|
|
725
|
+
return 0;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
case 'status': {
|
|
729
|
+
const parsed = parseJsonArgs(rest);
|
|
730
|
+
requireArity('1password status [--json]', parsed.positionals, 0);
|
|
731
|
+
const status = await inspectSyncStatus(undefined, { env, paths: getCodexPaths(env) });
|
|
732
|
+
write(io.stdout, parsed.json ? JSON.stringify(status, null, 2) : formatSyncStatus(status));
|
|
733
|
+
return 0;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
default:
|
|
737
|
+
throw new CxError(`unknown 1password command '${command}'`, 2);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
594
741
|
async function handleSyncCommand(
|
|
595
742
|
args: readonly string[],
|
|
596
743
|
env: NodeJS.ProcessEnv,
|
|
@@ -604,7 +751,12 @@ async function handleSyncCommand(
|
|
|
604
751
|
|
|
605
752
|
switch (command) {
|
|
606
753
|
case 'push': {
|
|
607
|
-
requireArity('sync push <account
|
|
754
|
+
requireArity('sync push <account>|--all', rest, 1);
|
|
755
|
+
if (rest[0] === '--all') {
|
|
756
|
+
const results = await syncPushAllAccounts({ env, paths: getCodexPaths(env) });
|
|
757
|
+
write(io.stdout, `pushed profiles: ${results.length > 0 ? results.map((entry) => entry.account).join(', ') : '(none)'}`);
|
|
758
|
+
return 0;
|
|
759
|
+
}
|
|
608
760
|
const account = rest[0] ?? '';
|
|
609
761
|
const result = await syncPushAccount(account, { env, paths: getCodexPaths(env) });
|
|
610
762
|
write(io.stdout, `pushed account '${result.account}' to 1Password item '${result.item}'`);
|
|
@@ -614,13 +766,33 @@ async function handleSyncCommand(
|
|
|
614
766
|
}
|
|
615
767
|
|
|
616
768
|
case 'pull': {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const
|
|
769
|
+
let force = false;
|
|
770
|
+
const positionals: string[] = [];
|
|
771
|
+
for (const arg of rest) {
|
|
772
|
+
if (arg === '--force') {
|
|
773
|
+
force = true;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (arg !== '--all' && arg.startsWith('--')) {
|
|
777
|
+
throw new CxError(`unknown option '${arg}'`, 2);
|
|
778
|
+
}
|
|
779
|
+
positionals.push(arg);
|
|
780
|
+
}
|
|
781
|
+
requireArity('sync pull <account>|--all [--force]', positionals, 1);
|
|
782
|
+
if (positionals[0] === '--all') {
|
|
783
|
+
const results = await syncPullAllAccounts({
|
|
784
|
+
env,
|
|
785
|
+
paths: getCodexPaths(env),
|
|
786
|
+
force,
|
|
787
|
+
});
|
|
788
|
+
write(io.stdout, `pulled profiles: ${results.length > 0 ? results.map((entry) => entry.account).join(', ') : '(none)'}`);
|
|
789
|
+
return 0;
|
|
790
|
+
}
|
|
791
|
+
const account = positionals[0] ?? '';
|
|
620
792
|
const result = await syncPullAccount(account, {
|
|
621
793
|
env,
|
|
622
794
|
paths: getCodexPaths(env),
|
|
623
|
-
force
|
|
795
|
+
force,
|
|
624
796
|
});
|
|
625
797
|
write(io.stdout, `pulled 1Password item '${result.item}' into account '${result.account}'`);
|
|
626
798
|
write(io.stdout, `account file: ${result.accountFile}`);
|
|
@@ -682,7 +854,7 @@ export async function main(
|
|
|
682
854
|
}
|
|
683
855
|
|
|
684
856
|
validateAccountName(first);
|
|
685
|
-
await
|
|
857
|
+
await useAccountWithRemoteFallback(first, env, io);
|
|
686
858
|
write(io.stdout, `→ codex on '${first}'`);
|
|
687
859
|
return await runCodex(rest, { env });
|
|
688
860
|
}
|
|
@@ -708,7 +880,7 @@ export async function main(
|
|
|
708
880
|
case 'use': {
|
|
709
881
|
requireArity('use <name>', rest, 1);
|
|
710
882
|
const name = rest[0] ?? '';
|
|
711
|
-
await
|
|
883
|
+
await useAccountWithRemoteFallback(name, env, io);
|
|
712
884
|
write(io.stdout, `active codex account: ${name}`);
|
|
713
885
|
return 0;
|
|
714
886
|
}
|
|
@@ -749,7 +921,7 @@ export async function main(
|
|
|
749
921
|
case 'run': {
|
|
750
922
|
const parsed = parseRunArgs(rest);
|
|
751
923
|
if (parsed.account) {
|
|
752
|
-
await
|
|
924
|
+
await useAccountWithRemoteFallback(parsed.account, env, io);
|
|
753
925
|
write(io.stdout, `→ codex on '${parsed.account}'`);
|
|
754
926
|
}
|
|
755
927
|
return await runCodex(parsed.codexArgs, { env });
|
|
@@ -768,6 +940,9 @@ export async function main(
|
|
|
768
940
|
case 'hermes':
|
|
769
941
|
return await handleHermesCommand(rest, env, io);
|
|
770
942
|
|
|
943
|
+
case '1password':
|
|
944
|
+
return await handleOnePasswordCommand(rest, env, io);
|
|
945
|
+
|
|
771
946
|
case 'remote':
|
|
772
947
|
return await handleRemoteCommand(rest, env, io);
|
|
773
948
|
|
package/src/index.ts
CHANGED
|
@@ -41,8 +41,12 @@ export {
|
|
|
41
41
|
getRemoteConfigPath,
|
|
42
42
|
inspectRemoteStatus,
|
|
43
43
|
inspectSyncStatus,
|
|
44
|
+
listRemoteAccountNames,
|
|
44
45
|
readRemoteConfig,
|
|
46
|
+
setupOnePasswordProfiles,
|
|
47
|
+
syncPullAllAccounts,
|
|
45
48
|
syncPullAccount,
|
|
49
|
+
syncPushAllAccounts,
|
|
46
50
|
syncPushAccount,
|
|
47
51
|
} from './remote.js';
|
|
48
52
|
|
|
@@ -80,6 +84,8 @@ export type {
|
|
|
80
84
|
RemotePathOptions,
|
|
81
85
|
RemotePresence,
|
|
82
86
|
RemoteStatus,
|
|
87
|
+
SetupOnePasswordProfilesInput,
|
|
88
|
+
SetupOnePasswordProfilesResult,
|
|
83
89
|
SyncPullResult,
|
|
84
90
|
SyncPushResult,
|
|
85
91
|
SyncStatus,
|
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';
|