Skip to content

Commit b08d88c

Browse files
dgouveaclaude
andcommitted
Support multiple name/value pairs in type command for batched secret resolution
The type command now accepts multiple --name and --value flags, allowing fields from the same 1Password item to be resolved in a single op call (one biometric prompt). SecretResolver groups secret:// references by provider+vault+item and batches the resolution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d830758 commit b08d88c

5 files changed

Lines changed: 142 additions & 68 deletions

File tree

package/.aux4

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@
279279
{
280280
"name": "type",
281281
"execute": [
282-
"node ${packageDir}/lib/aux4-browser.mjs type values(session,name,value,role)"
282+
"node ${packageDir}/lib/aux4-browser.mjs type values(session,name*,value*,role)"
283283
],
284284
"help": {
285285
"text": "Type text into an input field",
@@ -292,12 +292,14 @@
292292
{
293293
"name": "name",
294294
"text": "Accessible name of the field",
295-
"required": true
295+
"required": true,
296+
"multiple": true
296297
},
297298
{
298299
"name": "value",
299-
"text": "Text to type",
300-
"required": true
300+
"text": "Text to type or secret:// reference",
301+
"required": true,
302+
"multiple": true
301303
},
302304
{
303305
"name": "role",

package/lib/aux4-browser.mjs

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -943,48 +943,78 @@ async function GetItemsCommand(params) {
943943
}
944944
}
945945

946-
function resolveSecret(value) {
947-
if (!value || !value.startsWith("secret://")) {
948-
return value;
949-
}
946+
function resolveSecrets(values) {
947+
const results = new Array(values.length);
948+
const groups = new Map();
949+
950+
for (let i = 0; i < values.length; i++) {
951+
const value = values[i];
952+
if (!value || !value.startsWith("secret://")) {
953+
results[i] = value;
954+
continue;
955+
}
956+
957+
const path = value.replace("secret://", "");
958+
const parts = path.split("/");
950959

951-
const path = value.replace("secret://", "");
952-
const parts = path.split("/");
960+
if (parts.length < 4) {
961+
throw new Error(`Invalid secret reference: ${value}. Expected format: secret://<provider>/<vault>/<item>/<field>`);
962+
}
963+
964+
const provider = parts[0];
965+
const field = parts[parts.length - 1];
966+
const ref = parts.slice(1, parts.length - 1).join("/");
967+
const key = `${provider}:${ref}`;
953968

954-
if (parts.length < 4) {
955-
throw new Error(`Invalid secret reference: ${value}. Expected format: secret://<provider>/<vault>/<item>/<field>`);
969+
if (!groups.has(key)) {
970+
groups.set(key, { provider, ref, fields: [], indices: [] });
971+
}
972+
groups.get(key).fields.push(field);
973+
groups.get(key).indices.push(i);
956974
}
957975

958-
const provider = parts[0];
959-
const field = parts[parts.length - 1];
960-
const ref = parts.slice(1, parts.length - 1).join("/");
961-
962-
try {
963-
const output = execSync(
964-
`aux4 secret ${provider} get --ref "${ref}" --fields "${field}"`,
965-
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
966-
).trim();
967-
968-
const json = JSON.parse(output);
969-
return json[field];
970-
} catch (e) {
971-
if (e.status === 127) {
972-
throw new Error(`Secret provider 'aux4/secret-${provider}' is not installed. Install it with: aux4 aux4 pkger install aux4/secret-${provider}`);
976+
for (const [, group] of groups) {
977+
const fields = group.fields.join(",");
978+
try {
979+
const output = execSync(
980+
`aux4 secret ${group.provider} get --ref "${group.ref}" --fields "${fields}"`,
981+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
982+
).trim();
983+
984+
const json = JSON.parse(output);
985+
for (let j = 0; j < group.fields.length; j++) {
986+
results[group.indices[j]] = json[group.fields[j]];
987+
}
988+
} catch (e) {
989+
if (e.status === 127) {
990+
throw new Error(`Secret provider 'aux4/secret-${group.provider}' is not installed. Install it with: aux4 aux4 pkger install aux4/secret-${group.provider}`);
991+
}
992+
throw new Error(`Failed to resolve secret: ${e.stderr ? e.stderr.trim() : e.message}`);
973993
}
974-
throw new Error(`Failed to resolve secret: ${e.stderr ? e.stderr.trim() : e.message}`);
975994
}
995+
996+
return results;
976997
}
977998

978999
async function TypeCommand(params) {
979-
const value = resolveSecret(params.value);
1000+
const names = Array.isArray(params.name) ? params.name : [params.name];
1001+
const values = Array.isArray(params.value) ? params.value : [params.value];
1002+
1003+
if (names.length !== values.length) {
1004+
throw new Error(`Mismatched fields: ${names.length} name(s) but ${values.length} value(s)`);
1005+
}
1006+
1007+
const resolved = resolveSecrets(values);
9801008
const client = new DaemonClient();
981-
await client.send("type", {
982-
session: params.session,
983-
name: params.name,
984-
value: value,
985-
role: params.role
986-
});
987-
// No output on success
1009+
1010+
for (let i = 0; i < names.length; i++) {
1011+
await client.send("type", {
1012+
session: params.session,
1013+
name: names[i],
1014+
value: resolved[i],
1015+
role: params.role
1016+
});
1017+
}
9881018
}
9891019

9901020
async function ScrollCommand(params) {
@@ -1440,6 +1470,10 @@ if (!command) {
14401470
const params = {};
14411471
command.args.forEach((name, i) => {
14421472
if (values[i] !== undefined && values[i] !== "") {
1473+
try {
1474+
const parsed = JSON.parse(values[i]);
1475+
if (Array.isArray(parsed)) { params[name] = parsed; return; }
1476+
} catch {}
14431477
params[name] = values[i];
14441478
}
14451479
});

src/bin/executable.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ if (!command) {
8888
const params = {};
8989
command.args.forEach((name, i) => {
9090
if (values[i] !== undefined && values[i] !== "") {
91+
try {
92+
const parsed = JSON.parse(values[i]);
93+
if (Array.isArray(parsed)) { params[name] = parsed; return; }
94+
} catch {}
9195
params[name] = values[i];
9296
}
9397
});

src/commands/TypeCommand.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
import { DaemonClient } from "../client/DaemonClient.js";
2-
import { resolveSecret } from "../lib/SecretResolver.js";
2+
import { resolveSecrets } from "../lib/SecretResolver.js";
33

44
export async function TypeCommand(params) {
5-
const value = resolveSecret(params.value);
5+
const names = Array.isArray(params.name) ? params.name : [params.name];
6+
const values = Array.isArray(params.value) ? params.value : [params.value];
7+
8+
if (names.length !== values.length) {
9+
throw new Error(`Mismatched fields: ${names.length} name(s) but ${values.length} value(s)`);
10+
}
11+
12+
const resolved = resolveSecrets(values);
613
const client = new DaemonClient();
7-
const result = await client.send("type", {
8-
session: params.session,
9-
name: params.name,
10-
value: value,
11-
role: params.role
12-
});
13-
// No output on success
14+
15+
for (let i = 0; i < names.length; i++) {
16+
await client.send("type", {
17+
session: params.session,
18+
name: names[i],
19+
value: resolved[i],
20+
role: params.role
21+
});
22+
}
1423
}

src/lib/SecretResolver.js

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,58 @@
11
import { execSync } from "child_process";
22

33
export function resolveSecret(value) {
4-
if (!value || !value.startsWith("secret://")) {
5-
return value;
6-
}
4+
return resolveSecrets([value])[0];
5+
}
6+
7+
export function resolveSecrets(values) {
8+
const results = new Array(values.length);
9+
const groups = new Map();
710

8-
const path = value.replace("secret://", "");
9-
const parts = path.split("/");
11+
for (let i = 0; i < values.length; i++) {
12+
const value = values[i];
13+
if (!value || !value.startsWith("secret://")) {
14+
results[i] = value;
15+
continue;
16+
}
17+
18+
const path = value.replace("secret://", "");
19+
const parts = path.split("/");
1020

11-
if (parts.length < 4) {
12-
throw new Error(`Invalid secret reference: ${value}. Expected format: secret://<provider>/<vault>/<item>/<field>`);
21+
if (parts.length < 4) {
22+
throw new Error(`Invalid secret reference: ${value}. Expected format: secret://<provider>/<vault>/<item>/<field>`);
23+
}
24+
25+
const provider = parts[0];
26+
const field = parts[parts.length - 1];
27+
const ref = parts.slice(1, parts.length - 1).join("/");
28+
const key = `${provider}:${ref}`;
29+
30+
if (!groups.has(key)) {
31+
groups.set(key, { provider, ref, fields: [], indices: [] });
32+
}
33+
groups.get(key).fields.push(field);
34+
groups.get(key).indices.push(i);
1335
}
1436

15-
const provider = parts[0];
16-
const field = parts[parts.length - 1];
17-
const ref = parts.slice(1, parts.length - 1).join("/");
18-
19-
try {
20-
const output = execSync(
21-
`aux4 secret ${provider} get --ref "${ref}" --fields "${field}"`,
22-
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
23-
).trim();
24-
25-
const json = JSON.parse(output);
26-
return json[field];
27-
} catch (e) {
28-
if (e.status === 127) {
29-
throw new Error(`Secret provider 'aux4/secret-${provider}' is not installed. Install it with: aux4 aux4 pkger install aux4/secret-${provider}`);
37+
for (const [, group] of groups) {
38+
const fields = group.fields.join(",");
39+
try {
40+
const output = execSync(
41+
`aux4 secret ${group.provider} get --ref "${group.ref}" --fields "${fields}"`,
42+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
43+
).trim();
44+
45+
const json = JSON.parse(output);
46+
for (let j = 0; j < group.fields.length; j++) {
47+
results[group.indices[j]] = json[group.fields[j]];
48+
}
49+
} catch (e) {
50+
if (e.status === 127) {
51+
throw new Error(`Secret provider 'aux4/secret-${group.provider}' is not installed. Install it with: aux4 aux4 pkger install aux4/secret-${group.provider}`);
52+
}
53+
throw new Error(`Failed to resolve secret: ${e.stderr ? e.stderr.trim() : e.message}`);
3054
}
31-
throw new Error(`Failed to resolve secret: ${e.stderr ? e.stderr.trim() : e.message}`);
3255
}
56+
57+
return results;
3358
}

0 commit comments

Comments
 (0)