@pinchpass/cli 1.0.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/SKILL.md +81 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +52 -0
- package/package.json +30 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pinchpass
|
|
3
|
+
description: Generate one-time E2E-encrypted secret request links via pinchpass. Use when an agent needs to collect API keys, tokens, passwords, connection strings, or any sensitive value from the user. Trigger on keywords: API key, secret, token, credentials, password, .env.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
homepage: https://github.com/rubybear-lgtm/PinchPass
|
|
6
|
+
metadata: { "openclaw": { "requires": { "bins": ["pinchpass"] } } }
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# pinchpass
|
|
10
|
+
|
|
11
|
+
Securely collect sensitive values from users with end-to-end encryption. The
|
|
12
|
+
relay never sees the secret — encryption happens in the browser, the key lives
|
|
13
|
+
only in the URL fragment.
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- An agent needs a value it does not have (API key, token, password, etc.)
|
|
18
|
+
- The user says "I need to give you a secret" or "where do I put my key?"
|
|
19
|
+
- A `.env` file needs to be populated with credentials
|
|
20
|
+
|
|
21
|
+
Do **not** use for non-sensitive plaintext — there is no reason to go through
|
|
22
|
+
E2E encryption for a public value.
|
|
23
|
+
|
|
24
|
+
## CLI
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pinchpass request <secret-name>... [flags]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Bare `pinchpass <name>` also works (auto-detects missing subcommand).
|
|
31
|
+
|
|
32
|
+
### Flags
|
|
33
|
+
|
|
34
|
+
| Flag | Default | Description |
|
|
35
|
+
|---------------|---------------|------------------------------------------|
|
|
36
|
+
| `-tunnel` | false | Open a bore.pub tunnel for a public URL |
|
|
37
|
+
| `-note` | — | Description shown on the form |
|
|
38
|
+
| `-out` | `.env` | Output .env file path |
|
|
39
|
+
| `-ttl` | 30 | Minutes until the link expires |
|
|
40
|
+
| `-port` | random | Local port to bind |
|
|
41
|
+
| `-listen-addr`| `127.0.0.1` | Address to listen on |
|
|
42
|
+
| `-json` | false | Machine-readable JSON output |
|
|
43
|
+
|
|
44
|
+
## Workflow
|
|
45
|
+
|
|
46
|
+
1. Run `pinchpass request <name> -json` to get a link
|
|
47
|
+
2. Give the link to the user (the fragment `#k=<hex>` is the decryption key)
|
|
48
|
+
3. User opens the link in a browser, fills in the value, submits
|
|
49
|
+
4. Server waits for the submission or TTL expiry
|
|
50
|
+
5. On success, the value is decrypted and written to `.env`
|
|
51
|
+
|
|
52
|
+
### JSON output mode
|
|
53
|
+
|
|
54
|
+
Use `-json` for machine parsing. The JSON is printed twice:
|
|
55
|
+
- **Before waiting**: `{ "success": true, "url": "...", "port": N }`
|
|
56
|
+
- **After completion**: `{ "success": true/false, "names": [...], "message": "..." }`
|
|
57
|
+
|
|
58
|
+
## Workflow example
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Collect an API key via public tunnel
|
|
62
|
+
pinchpass request GEMINI_API_KEY -tunnel -json
|
|
63
|
+
# → Prints link object with url field
|
|
64
|
+
# → Agent gives url to user
|
|
65
|
+
# → Agent waits for result
|
|
66
|
+
# → On completion, value in .env
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Edge cases
|
|
70
|
+
|
|
71
|
+
- **One-time use**: after the first POST, the token is consumed. Retry
|
|
72
|
+
generates a 404. If it fails, generate a new link.
|
|
73
|
+
- **TTL expiry**: server shuts down after the TTL. No value is saved and exit
|
|
74
|
+
code is 1. Generate a new link with a longer `-ttl` if needed.
|
|
75
|
+
- **No tunnel**: without `-tunnel`, the link is only reachable on
|
|
76
|
+
`127.0.0.1:<port>`. Use for local-only workflows or when the user is on the
|
|
77
|
+
same machine.
|
|
78
|
+
- **Bore.pub unreachable**: tunnel startup fails if bore.pub is down. Fall
|
|
79
|
+
back to local mode or retry.
|
|
80
|
+
- **Shell escaping**: values written to `.env` are shell-escaped for `\`,
|
|
81
|
+
`"`, `$`, and backtick.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare const _default: () => Promise<{
|
|
2
|
+
tool: {
|
|
3
|
+
request_secret: {
|
|
4
|
+
description: string;
|
|
5
|
+
args: {
|
|
6
|
+
names: import("zod").ZodArray<import("zod").ZodString>;
|
|
7
|
+
tunnel: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
8
|
+
note: import("zod").ZodOptional<import("zod").ZodString>;
|
|
9
|
+
ttl: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
10
|
+
};
|
|
11
|
+
execute(args: {
|
|
12
|
+
names: string[];
|
|
13
|
+
tunnel?: boolean | undefined;
|
|
14
|
+
note?: string | undefined;
|
|
15
|
+
ttl?: number | undefined;
|
|
16
|
+
}, context: import("@opencode-ai/plugin/tool").ToolContext): Promise<import("@opencode-ai/plugin/tool").ToolResult>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
export default async () => {
|
|
4
|
+
return {
|
|
5
|
+
tool: {
|
|
6
|
+
request_secret: tool({
|
|
7
|
+
description: "Generate a one-time E2E-encrypted secret request link. " +
|
|
8
|
+
"Use when you need to collect sensitive values (API keys, tokens, passwords) from the user. " +
|
|
9
|
+
"The tool starts a local server, prints a link, and blocks until the user submits or the TTL expires. " +
|
|
10
|
+
"On success the secret is saved to .env and the tool returns the result.",
|
|
11
|
+
args: {
|
|
12
|
+
names: tool.schema
|
|
13
|
+
.array(tool.schema.string())
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe("Secret name(s) to collect, e.g. ['GEMINI_API_KEY']"),
|
|
16
|
+
tunnel: tool.schema
|
|
17
|
+
.boolean()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Create a public URL via bore.pub tunnel (default: false)"),
|
|
20
|
+
note: tool.schema
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Description shown on the form to help the user"),
|
|
24
|
+
ttl: tool.schema
|
|
25
|
+
.number()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Minutes until the link expires (default: 30)"),
|
|
28
|
+
},
|
|
29
|
+
async execute(args) {
|
|
30
|
+
const cmd = ["pinchpass", "request", ...args.names, "-json"];
|
|
31
|
+
if (args.tunnel)
|
|
32
|
+
cmd.push("-tunnel");
|
|
33
|
+
if (args.note)
|
|
34
|
+
cmd.push("-note", args.note);
|
|
35
|
+
if (args.ttl)
|
|
36
|
+
cmd.push("-ttl", String(args.ttl));
|
|
37
|
+
try {
|
|
38
|
+
const stdout = execSync(cmd.join(" "), {
|
|
39
|
+
encoding: "utf-8",
|
|
40
|
+
timeout: (args.ttl ?? 30) * 60 * 1000,
|
|
41
|
+
});
|
|
42
|
+
return stdout;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
46
|
+
return `Failed to generate secret request: ${msg}`;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pinchpass/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "One-time E2E-encrypted secret request links for OpenClaw agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"SKILL.md"
|
|
10
|
+
],
|
|
11
|
+
"openclaw": {
|
|
12
|
+
"extensions": [
|
|
13
|
+
"./dist/index.js"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@opencode-ai/plugin": "^1.16.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^25.9.2",
|
|
25
|
+
"typescript": "^5.9.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|