@phnx-labs/agents-cli 1.20.0 → 1.20.4

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 (111) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +4 -4
  3. package/dist/commands/cli.js +3 -3
  4. package/dist/commands/cloud.js +1 -1
  5. package/dist/commands/commands.js +24 -7
  6. package/dist/commands/exec.js +36 -16
  7. package/dist/commands/feedback.d.ts +7 -0
  8. package/dist/commands/feedback.js +89 -0
  9. package/dist/commands/helper.d.ts +12 -0
  10. package/dist/commands/helper.js +87 -0
  11. package/dist/commands/hooks.js +86 -7
  12. package/dist/commands/import.js +90 -37
  13. package/dist/commands/mcp.js +166 -10
  14. package/dist/commands/packages.js +196 -27
  15. package/dist/commands/permissions.js +21 -6
  16. package/dist/commands/profiles.d.ts +8 -0
  17. package/dist/commands/profiles.js +117 -4
  18. package/dist/commands/pull.js +4 -4
  19. package/dist/commands/routines.js +6 -6
  20. package/dist/commands/rules.js +8 -4
  21. package/dist/commands/secrets-migrate.d.ts +24 -0
  22. package/dist/commands/secrets-migrate.js +198 -0
  23. package/dist/commands/secrets-sync.d.ts +11 -0
  24. package/dist/commands/secrets-sync.js +155 -0
  25. package/dist/commands/secrets.js +74 -39
  26. package/dist/commands/skills.js +22 -5
  27. package/dist/commands/subagents.js +69 -49
  28. package/dist/commands/teams.js +48 -10
  29. package/dist/commands/utils.d.ts +33 -0
  30. package/dist/commands/utils.js +139 -0
  31. package/dist/commands/versions.js +4 -4
  32. package/dist/commands/view.d.ts +6 -0
  33. package/dist/commands/view.js +169 -8
  34. package/dist/commands/workflows.js +29 -6
  35. package/dist/index.js +4 -0
  36. package/dist/lib/acp/client.js +6 -1
  37. package/dist/lib/agents.d.ts +4 -0
  38. package/dist/lib/agents.js +41 -17
  39. package/dist/lib/auto-pull-worker.js +18 -1
  40. package/dist/lib/browser/chrome.js +4 -0
  41. package/dist/lib/browser/drivers/ssh.js +1 -1
  42. package/dist/lib/browser/profiles.d.ts +3 -3
  43. package/dist/lib/browser/profiles.js +3 -3
  44. package/dist/lib/browser/service.js +19 -0
  45. package/dist/lib/browser/types.d.ts +4 -4
  46. package/dist/lib/cli-resources.d.ts +36 -8
  47. package/dist/lib/cli-resources.js +268 -46
  48. package/dist/lib/cloud/factory.d.ts +1 -1
  49. package/dist/lib/cloud/factory.js +1 -1
  50. package/dist/lib/events.d.ts +16 -2
  51. package/dist/lib/events.js +33 -2
  52. package/dist/lib/exec.d.ts +39 -11
  53. package/dist/lib/exec.js +90 -31
  54. package/dist/lib/help.js +11 -5
  55. package/dist/lib/hooks/cache.d.ts +38 -0
  56. package/dist/lib/hooks/cache.js +242 -0
  57. package/dist/lib/hooks/profile.d.ts +33 -0
  58. package/dist/lib/hooks/profile.js +129 -0
  59. package/dist/lib/hooks.d.ts +0 -10
  60. package/dist/lib/hooks.js +68 -15
  61. package/dist/lib/import.d.ts +21 -0
  62. package/dist/lib/import.js +55 -2
  63. package/dist/lib/mcp.d.ts +15 -0
  64. package/dist/lib/mcp.js +40 -0
  65. package/dist/lib/permissions.d.ts +13 -0
  66. package/dist/lib/permissions.js +51 -1
  67. package/dist/lib/plugin-marketplace.d.ts +10 -0
  68. package/dist/lib/plugin-marketplace.js +47 -1
  69. package/dist/lib/plugins.js +15 -1
  70. package/dist/lib/profiles-presets.d.ts +26 -0
  71. package/dist/lib/profiles-presets.js +187 -8
  72. package/dist/lib/profiles.d.ts +34 -0
  73. package/dist/lib/profiles.js +112 -1
  74. package/dist/lib/pty-server.js +27 -3
  75. package/dist/lib/routines-format.d.ts +17 -5
  76. package/dist/lib/routines-format.js +37 -16
  77. package/dist/lib/routines.d.ts +1 -1
  78. package/dist/lib/routines.js +2 -2
  79. package/dist/lib/runner.js +64 -10
  80. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  81. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  82. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -9
  83. package/dist/lib/secrets/bundles.d.ts +18 -22
  84. package/dist/lib/secrets/bundles.js +75 -99
  85. package/dist/lib/secrets/index.d.ts +51 -27
  86. package/dist/lib/secrets/index.js +147 -156
  87. package/dist/lib/secrets/install-helper.d.ts +45 -0
  88. package/dist/lib/secrets/install-helper.js +165 -0
  89. package/dist/lib/secrets/linux.js +4 -4
  90. package/dist/lib/secrets/sync.d.ts +56 -0
  91. package/dist/lib/secrets/sync.js +180 -0
  92. package/dist/lib/session/render.js +4 -4
  93. package/dist/lib/session/types.d.ts +1 -1
  94. package/dist/lib/shims.d.ts +4 -1
  95. package/dist/lib/shims.js +5 -35
  96. package/dist/lib/state.d.ts +14 -1
  97. package/dist/lib/state.js +49 -5
  98. package/dist/lib/teams/agents.d.ts +5 -4
  99. package/dist/lib/teams/agents.js +47 -21
  100. package/dist/lib/teams/api.d.ts +2 -1
  101. package/dist/lib/teams/api.js +4 -3
  102. package/dist/lib/types.d.ts +57 -1
  103. package/dist/lib/types.js +2 -0
  104. package/dist/lib/usage.d.ts +27 -2
  105. package/dist/lib/usage.js +100 -17
  106. package/dist/lib/versions.d.ts +35 -1
  107. package/dist/lib/versions.js +288 -64
  108. package/package.json +13 -12
  109. package/scripts/install-helper.js +97 -0
  110. package/scripts/postinstall.js +16 -0
  111. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `agents secrets migrate-acl` — refresh existing keychain items so they pick
3
+ * up the new SecAccess ACL written by the signed Agents CLI.app helper.
4
+ *
5
+ * Items created before 1.19.2 (or by `security add-generic-password` directly)
6
+ * may carry the legacy "this-app-only" ACL that prompts the user for a
7
+ * password on every read. Re-writing them through the helper bakes in the
8
+ * empty trusted-app ACL that suppresses the prompt and lets the helper read
9
+ * them under LocalAuthentication instead.
10
+ *
11
+ * Sequence per item:
12
+ * 1. Read the current value (no auth prompt path — uses the unauthenticated
13
+ * `security` CLI for non-sync items, helper `get` for sync items).
14
+ * 2. Append (item, value, sync) to an encrypted backup before any writes.
15
+ * 3. Delete + rewrite via the helper so macOS hands us a fresh ACL on the
16
+ * new item.
17
+ * 4. Read back via the helper to verify the value round-trips.
18
+ *
19
+ * `--dry-run` (default) reports the planned actions. `--commit` performs the
20
+ * writes and produces the backup.
21
+ */
22
+ import type { Command } from 'commander';
23
+ /** Register `agents secrets migrate-acl` on the parent secrets Command. */
24
+ export declare function registerSecretsMigrateAclCommand(secrets: Command): void;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * `agents secrets migrate-acl` — refresh existing keychain items so they pick
3
+ * up the new SecAccess ACL written by the signed Agents CLI.app helper.
4
+ *
5
+ * Items created before 1.19.2 (or by `security add-generic-password` directly)
6
+ * may carry the legacy "this-app-only" ACL that prompts the user for a
7
+ * password on every read. Re-writing them through the helper bakes in the
8
+ * empty trusted-app ACL that suppresses the prompt and lets the helper read
9
+ * them under LocalAuthentication instead.
10
+ *
11
+ * Sequence per item:
12
+ * 1. Read the current value (no auth prompt path — uses the unauthenticated
13
+ * `security` CLI for non-sync items, helper `get` for sync items).
14
+ * 2. Append (item, value, sync) to an encrypted backup before any writes.
15
+ * 3. Delete + rewrite via the helper so macOS hands us a fresh ACL on the
16
+ * new item.
17
+ * 4. Read back via the helper to verify the value round-trips.
18
+ *
19
+ * `--dry-run` (default) reports the planned actions. `--commit` performs the
20
+ * writes and produces the backup.
21
+ */
22
+ import chalk from 'chalk';
23
+ import * as crypto from 'crypto';
24
+ import * as fs from 'fs';
25
+ import * as path from 'path';
26
+ import { deleteKeychainToken, getKeychainToken, hasKeychainToken, listKeychainItems, setKeychainToken, } from '../lib/secrets/index.js';
27
+ import { getBackupsDir } from '../lib/state.js';
28
+ import { encryptBlob, MIN_PASSPHRASE_LEN } from '../lib/secrets/sync.js';
29
+ import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
30
+ const ITEM_PREFIX = 'agents-cli.';
31
+ function enumerateItems() {
32
+ const seen = new Map();
33
+ for (const item of listKeychainItems(ITEM_PREFIX)) {
34
+ // hasKeychainToken with sync=false probes the non-synced keychain; the
35
+ // helper's list returns both. We don't try to distinguish — re-write with
36
+ // sync=false by default and only flip to sync=true if the value is only
37
+ // readable via the synced-only probe.
38
+ const localExists = hasKeychainToken(item);
39
+ seen.set(item, { item, sync: !localExists });
40
+ }
41
+ return [...seen.values()];
42
+ }
43
+ function writeEncryptedBackup(records, passphrase) {
44
+ const dir = getBackupsDir();
45
+ fs.mkdirSync(dir, { recursive: true });
46
+ const iso = new Date().toISOString().replace(/[:.]/g, '-');
47
+ const file = path.join(dir, `keychain-pre-migrate-${iso}.json.enc`);
48
+ const envelope = encryptBlob(JSON.stringify({ v: 1, records }), passphrase);
49
+ fs.writeFileSync(file, JSON.stringify(envelope), { mode: 0o600 });
50
+ return file;
51
+ }
52
+ async function promptPassphrase() {
53
+ if (!isInteractiveTerminal()) {
54
+ throw new Error('A backup passphrase is required. Run from a TTY or set AGENTS_BACKUP_PASSPHRASE.');
55
+ }
56
+ const { password } = await import('@inquirer/prompts');
57
+ const first = await password({ message: 'Backup passphrase (used to encrypt the pre-migration snapshot)', mask: true });
58
+ if (first.length < MIN_PASSPHRASE_LEN)
59
+ throw new Error(`Passphrase must be at least ${MIN_PASSPHRASE_LEN} characters.`);
60
+ const second = await password({ message: 'Confirm passphrase', mask: true });
61
+ if (first !== second)
62
+ throw new Error('Passphrases do not match.');
63
+ return first;
64
+ }
65
+ function migrateOne(record) {
66
+ const { item, value } = record;
67
+ // Delete + re-add to force macOS to bind a fresh ACL on the new item.
68
+ // SecItemUpdate preserves the existing ACL, so an in-place rewrite would
69
+ // not fix the legacy "enter password" prompt.
70
+ try {
71
+ deleteKeychainToken(item);
72
+ }
73
+ catch (err) {
74
+ return { item, status: 'write-failed', detail: `delete: ${err.message}` };
75
+ }
76
+ try {
77
+ setKeychainToken(item, value);
78
+ }
79
+ catch (err) {
80
+ // Try to restore the old value so we don't lose data on a write failure.
81
+ // The backup is the durable safety net; this is best-effort UX.
82
+ try {
83
+ setKeychainToken(item, value);
84
+ }
85
+ catch { /* swallow */ }
86
+ return { item, status: 'write-failed', detail: `set: ${err.message}` };
87
+ }
88
+ let readBack;
89
+ try {
90
+ readBack = getKeychainToken(item);
91
+ }
92
+ catch (err) {
93
+ return { item, status: 'verify-failed', detail: `read-back: ${err.message}` };
94
+ }
95
+ if (readBack !== value) {
96
+ return { item, status: 'verify-failed', detail: 'value mismatch after rewrite' };
97
+ }
98
+ return { item, status: 'ok' };
99
+ }
100
+ /** Register `agents secrets migrate-acl` on the parent secrets Command. */
101
+ export function registerSecretsMigrateAclCommand(secrets) {
102
+ secrets
103
+ .command('migrate-acl')
104
+ .description('Refresh existing keychain ACLs to use the signed Agents CLI helper. Dry-run by default.')
105
+ .option('--commit', 'Perform writes (default is dry-run reporting only)')
106
+ .option('--prefix <p>', `Restrict to items beginning with PREFIX (default ${ITEM_PREFIX})`, ITEM_PREFIX)
107
+ .option('--passphrase-env <var>', 'Read the backup passphrase from this env var instead of prompting')
108
+ .action(async (opts) => {
109
+ try {
110
+ if (process.platform !== 'darwin') {
111
+ throw new Error('migrate-acl is macOS-only. Linux items already use the keyring-native ACL model.');
112
+ }
113
+ const prefix = opts.prefix ?? ITEM_PREFIX;
114
+ if (!prefix.startsWith(ITEM_PREFIX)) {
115
+ throw new Error(`--prefix must start with '${ITEM_PREFIX}' to avoid touching unrelated Keychain items (got '${prefix}').`);
116
+ }
117
+ const items = listKeychainItems(prefix).map((item) => {
118
+ const localExists = hasKeychainToken(item);
119
+ return { item, sync: !localExists };
120
+ });
121
+ if (items.length === 0) {
122
+ console.log(chalk.gray(`No keychain items with prefix '${prefix}'.`));
123
+ return;
124
+ }
125
+ console.log(chalk.bold(`Found ${items.length} item(s) under '${prefix}'.`));
126
+ if (!opts.commit) {
127
+ for (const { item, sync } of items) {
128
+ console.log(` ${chalk.cyan(item)} ${chalk.gray(sync ? '(synced)' : '(local)')}`);
129
+ }
130
+ console.log();
131
+ console.log(chalk.gray('Dry-run — pass --commit to perform the migration.'));
132
+ return;
133
+ }
134
+ // Commit phase. Snapshot every value first, encrypt, then mutate.
135
+ const records = [];
136
+ for (const { item, sync } of items) {
137
+ try {
138
+ const value = getKeychainToken(item);
139
+ records.push({ item, sync, value });
140
+ }
141
+ catch (err) {
142
+ console.error(chalk.red(`Skipping '${item}': read failed (${err.message}).`));
143
+ }
144
+ }
145
+ if (records.length === 0) {
146
+ console.error(chalk.red('No items could be read. Aborting before any writes.'));
147
+ process.exit(1);
148
+ }
149
+ const passphrase = opts.passphraseEnv
150
+ ? (() => {
151
+ const v = process.env[opts.passphraseEnv];
152
+ if (!v)
153
+ throw new Error(`Env var '${opts.passphraseEnv}' not set.`);
154
+ if (v.length < MIN_PASSPHRASE_LEN)
155
+ throw new Error(`Passphrase must be at least ${MIN_PASSPHRASE_LEN} characters.`);
156
+ return v;
157
+ })()
158
+ : await promptPassphrase();
159
+ const backupPath = writeEncryptedBackup(records, passphrase);
160
+ // Compute a quick fingerprint so the user can sanity-check recovery without
161
+ // decrypting. Hash of (count, sorted item names).
162
+ const fingerprint = crypto
163
+ .createHash('sha256')
164
+ .update(records.length + '\n' + records.map((r) => r.item).sort().join('\n'))
165
+ .digest('hex')
166
+ .slice(0, 12);
167
+ console.log(chalk.green(`Encrypted backup written to ${backupPath} (sha256-12: ${fingerprint}).`));
168
+ const results = [];
169
+ for (const record of records) {
170
+ const r = migrateOne(record);
171
+ results.push(r);
172
+ if (r.status === 'ok') {
173
+ console.log(` ${chalk.green('ok')} ${record.item}`);
174
+ }
175
+ else {
176
+ console.log(` ${chalk.red(r.status)} ${record.item} ${chalk.gray(r.detail ?? '')}`);
177
+ }
178
+ }
179
+ const okCount = results.filter((r) => r.status === 'ok').length;
180
+ const failCount = results.length - okCount;
181
+ console.log();
182
+ if (failCount === 0) {
183
+ console.log(chalk.green(`Migrated ${okCount}/${results.length} item(s).`));
184
+ }
185
+ else {
186
+ console.error(chalk.yellow(`Migrated ${okCount}/${results.length} item(s); ${failCount} failed.`));
187
+ console.error(chalk.gray(`Restore from ${backupPath} using the backup passphrase if needed.`));
188
+ process.exit(1);
189
+ }
190
+ }
191
+ catch (err) {
192
+ if (isPromptCancelled(err))
193
+ return;
194
+ console.error(chalk.red(err.message));
195
+ process.exit(1);
196
+ }
197
+ });
198
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `agents secrets push|pull` subcommands.
3
+ *
4
+ * Replaces iCloud Keychain as the cross-device sync mechanism with explicit
5
+ * encrypted-at-rest sync against api.prix.dev. Plaintext never leaves the
6
+ * machine — bundle contents are sealed with AES-256-GCM under a user-supplied
7
+ * passphrase before upload.
8
+ */
9
+ import type { Command } from 'commander';
10
+ /** Register `agents secrets push|pull|remote-list` on the parent secrets Command. */
11
+ export declare function registerSecretsSyncCommands(secrets: Command): void;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * `agents secrets push|pull` subcommands.
3
+ *
4
+ * Replaces iCloud Keychain as the cross-device sync mechanism with explicit
5
+ * encrypted-at-rest sync against api.prix.dev. Plaintext never leaves the
6
+ * machine — bundle contents are sealed with AES-256-GCM under a user-supplied
7
+ * passphrase before upload.
8
+ */
9
+ import chalk from 'chalk';
10
+ import { listRemoteBundles, MIN_PASSPHRASE_LEN, pullBundle, pushBundle, } from '../lib/secrets/sync.js';
11
+ import { bundleExists, listBundles } from '../lib/secrets/bundles.js';
12
+ import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
13
+ async function promptPassphrase(message, confirm = false) {
14
+ if (!isInteractiveTerminal()) {
15
+ throw new Error('A sync passphrase is required. Run from a TTY, or set AGENTS_SECRETS_PASSPHRASE.');
16
+ }
17
+ const { password } = await import('@inquirer/prompts');
18
+ const first = await password({ message, mask: true });
19
+ if (first.length < MIN_PASSPHRASE_LEN)
20
+ throw new Error(`Passphrase must be at least ${MIN_PASSPHRASE_LEN} characters.`);
21
+ if (!confirm)
22
+ return first;
23
+ const second = await password({ message: 'Confirm passphrase', mask: true });
24
+ if (first !== second)
25
+ throw new Error('Passphrases do not match.');
26
+ return first;
27
+ }
28
+ // Print the env-var-source warning at most once per process so a `--all` push
29
+ // over many bundles doesn't flood stderr with the same notice.
30
+ let envPassphraseWarned = false;
31
+ function passphraseFromEnvOrPrompt(confirm) {
32
+ const fromEnv = process.env.AGENTS_SECRETS_PASSPHRASE;
33
+ if (fromEnv) {
34
+ if (fromEnv.length < MIN_PASSPHRASE_LEN) {
35
+ return Promise.reject(new Error(`Passphrase must be at least ${MIN_PASSPHRASE_LEN} characters.`));
36
+ }
37
+ if (!envPassphraseWarned) {
38
+ envPassphraseWarned = true;
39
+ process.stderr.write(chalk.yellow('warn: using AGENTS_SECRETS_PASSPHRASE. Env vars are readable by other same-user processes ' +
40
+ '(/proc, ps, crash dumps, CI logs) — rotate the passphrase after CI use.\n'));
41
+ }
42
+ return Promise.resolve(fromEnv);
43
+ }
44
+ return promptPassphrase('Sync passphrase', confirm);
45
+ }
46
+ /** Strip C0/C1 control bytes from server-supplied strings before terminal print. */
47
+ function safePrint(s) {
48
+ return s.replace(/[\x00-\x08\x0B-\x1F\x7F-\x9F]/g, '');
49
+ }
50
+ /** Register `agents secrets push|pull|remote-list` on the parent secrets Command. */
51
+ export function registerSecretsSyncCommands(secrets) {
52
+ secrets
53
+ .command('push [name]')
54
+ .description('Encrypt a local bundle and upload it to api.prix.dev (replaces iCloud Keychain sync).')
55
+ .option('--all', 'Push every local bundle (each prompts independently if no passphrase env var is set)')
56
+ .action(async (name, opts) => {
57
+ try {
58
+ if (opts.all) {
59
+ const bundles = listBundles();
60
+ if (bundles.length === 0) {
61
+ console.log(chalk.gray('No local bundles to push.'));
62
+ return;
63
+ }
64
+ const passphrase = await passphraseFromEnvOrPrompt(true);
65
+ for (const b of bundles) {
66
+ try {
67
+ const { updated_at } = await pushBundle(b.name, { passphrase });
68
+ console.log(chalk.green(`Pushed '${b.name}' (updated_at=${updated_at}).`));
69
+ }
70
+ catch (err) {
71
+ console.error(chalk.red(`Failed to push '${b.name}': ${err.message}`));
72
+ }
73
+ }
74
+ return;
75
+ }
76
+ if (!name) {
77
+ throw new Error('Bundle name required. Try: agents secrets push <name> | agents secrets push --all');
78
+ }
79
+ if (!bundleExists(name)) {
80
+ throw new Error(`Bundle '${name}' not found locally.`);
81
+ }
82
+ const passphrase = await passphraseFromEnvOrPrompt(true);
83
+ const { updated_at } = await pushBundle(name, { passphrase });
84
+ console.log(chalk.green(`Pushed '${name}' to api.prix.dev (updated_at=${updated_at}).`));
85
+ console.log(chalk.gray('Remember the passphrase — it is required to pull this bundle on another machine.'));
86
+ }
87
+ catch (err) {
88
+ if (isPromptCancelled(err))
89
+ return;
90
+ console.error(chalk.red(err.message));
91
+ process.exit(1);
92
+ }
93
+ });
94
+ secrets
95
+ .command('pull [name]')
96
+ .description('Decrypt a remote bundle from api.prix.dev and restore it into the local keychain.')
97
+ .option('--all', 'Pull every bundle visible on the remote')
98
+ .option('--force', 'Overwrite a local bundle with the same name')
99
+ .action(async (name, opts) => {
100
+ try {
101
+ if (opts.all) {
102
+ const remote = await listRemoteBundles();
103
+ if (remote.length === 0) {
104
+ console.log(chalk.gray('No remote bundles found.'));
105
+ return;
106
+ }
107
+ const passphrase = await passphraseFromEnvOrPrompt(false);
108
+ for (const r of remote) {
109
+ try {
110
+ await pullBundle(r.name, { passphrase, force: opts.force });
111
+ console.log(chalk.green(`Pulled '${r.name}'.`));
112
+ }
113
+ catch (err) {
114
+ console.error(chalk.red(`Failed to pull '${r.name}': ${err.message}`));
115
+ }
116
+ }
117
+ return;
118
+ }
119
+ if (!name) {
120
+ throw new Error('Bundle name required. Try: agents secrets pull <name> | agents secrets pull --all');
121
+ }
122
+ const passphrase = await passphraseFromEnvOrPrompt(false);
123
+ const bundle = await pullBundle(name, { passphrase, force: opts.force });
124
+ const keyCount = Object.keys(bundle.vars).length;
125
+ console.log(chalk.green(`Pulled '${name}' (${keyCount} key${keyCount === 1 ? '' : 's'}) into local keychain.`));
126
+ }
127
+ catch (err) {
128
+ if (isPromptCancelled(err))
129
+ return;
130
+ console.error(chalk.red(err.message));
131
+ process.exit(1);
132
+ }
133
+ });
134
+ secrets
135
+ .command('remote-list')
136
+ .alias('remote-ls')
137
+ .description('List bundles currently stored on api.prix.dev for this account.')
138
+ .action(async () => {
139
+ try {
140
+ const remote = await listRemoteBundles();
141
+ if (remote.length === 0) {
142
+ console.log(chalk.gray('No remote bundles found.'));
143
+ return;
144
+ }
145
+ console.log(chalk.bold(`${'NAME'.padEnd(24)} UPDATED_AT`));
146
+ for (const r of remote) {
147
+ console.log(`${chalk.cyan(safePrint(r.name).padEnd(24))} ${chalk.gray(safePrint(r.updated_at))}`);
148
+ }
149
+ }
150
+ catch (err) {
151
+ console.error(chalk.red(err.message));
152
+ process.exit(1);
153
+ }
154
+ });
155
+ }