@1dolinski/fastforms 0.4.0 → 0.5.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/README.md +56 -39
- package/SKILL.md +23 -7
- package/bin/fastforms.js +74 -12
- package/lib/x402.js +112 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ Fill any form fast. Manage multiple personas locally, pick the right ones at fil
|
|
|
8
8
|
# 1. Create your first user + business persona
|
|
9
9
|
npx @1dolinski/fastforms init
|
|
10
10
|
|
|
11
|
-
# 2.
|
|
12
|
-
npx @1dolinski/fastforms
|
|
11
|
+
# 2. Or import from Twitter (requires PRIVATE_KEY for x402 payment)
|
|
12
|
+
npx @1dolinski/fastforms import twitter <your-handle>
|
|
13
13
|
|
|
14
14
|
# 3. Enable remote debugging in Chrome
|
|
15
15
|
# Open chrome://inspect/#remote-debugging and toggle it on
|
|
@@ -21,16 +21,17 @@ npx @1dolinski/fastforms fill https://example.com/apply
|
|
|
21
21
|
## How it works
|
|
22
22
|
|
|
23
23
|
1. **`fastforms init`** walks you through creating user + business personas interactively
|
|
24
|
-
2. **`fastforms
|
|
25
|
-
3.
|
|
26
|
-
4.
|
|
27
|
-
5.
|
|
24
|
+
2. **`fastforms import twitter`** pulls your profile from Twitter via x402 micropayment
|
|
25
|
+
3. **`fastforms add form`** captures the form's org, purpose, and form-specific answers
|
|
26
|
+
4. Personas are saved as individual JSON files in `.fastforms/`
|
|
27
|
+
5. **`fastforms fill <url>`** connects to Chrome, picks personas, fills by label matching
|
|
28
28
|
6. **Review and submit manually** in Chrome
|
|
29
29
|
|
|
30
30
|
## Requirements
|
|
31
31
|
|
|
32
32
|
- Chrome >= 144
|
|
33
33
|
- Node.js >= 18
|
|
34
|
+
- (Optional) `PRIVATE_KEY` env var for Twitter import — Base network wallet with USDC
|
|
34
35
|
|
|
35
36
|
## Persona types
|
|
36
37
|
|
|
@@ -40,13 +41,14 @@ npx @1dolinski/fastforms fill https://example.com/apply
|
|
|
40
41
|
| **Business** | What you're building | Company, product, traction, one-liner |
|
|
41
42
|
| **Form** | Who's asking & why | Org, purpose, form-specific answers |
|
|
42
43
|
|
|
43
|
-
Form persona facts **override** user/business data.
|
|
44
|
+
Form persona facts **override** user/business data. Form-specific questions like "why this accelerator" belong on the form persona, not your user persona.
|
|
44
45
|
|
|
45
46
|
## Commands
|
|
46
47
|
|
|
47
48
|
| Command | Description |
|
|
48
49
|
|---|---|
|
|
49
50
|
| `fastforms init` | Create your first user + business persona |
|
|
51
|
+
| `fastforms import twitter <handle>` | Import user persona from Twitter via x402 |
|
|
50
52
|
| `fastforms add user` | Add another user persona |
|
|
51
53
|
| `fastforms add business` | Add another business persona |
|
|
52
54
|
| `fastforms add form` | Add a form persona (org + answers) |
|
|
@@ -72,69 +74,84 @@ Form persona facts **override** user/business data. "What attracts you to Nitro"
|
|
|
72
74
|
```
|
|
73
75
|
.fastforms/
|
|
74
76
|
users/
|
|
75
|
-
|
|
76
|
-
work-
|
|
77
|
+
jane.json
|
|
78
|
+
work-jane.json
|
|
77
79
|
businesses/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
acme-labs.json
|
|
81
|
+
side-project.json
|
|
80
82
|
forms/
|
|
81
|
-
nitro-accelerator.json
|
|
82
83
|
yc-application.json
|
|
84
|
+
grant-proposal.json
|
|
83
85
|
defaults.json
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
### User persona (`users/
|
|
88
|
+
### User persona (`users/jane.json`)
|
|
87
89
|
|
|
88
90
|
```json
|
|
89
91
|
{
|
|
90
|
-
"name": "
|
|
91
|
-
"fullName": "
|
|
92
|
-
"email": "
|
|
93
|
-
"role": "Founder",
|
|
94
|
-
"location": "
|
|
95
|
-
"linkedIn": "linkedin.com/in/
|
|
96
|
-
"github": "github.com/
|
|
97
|
-
"bio": "
|
|
92
|
+
"name": "jane",
|
|
93
|
+
"fullName": "Jane Smith",
|
|
94
|
+
"email": "jane@example.com",
|
|
95
|
+
"role": "Founder & CEO",
|
|
96
|
+
"location": "San Francisco, CA",
|
|
97
|
+
"linkedIn": "linkedin.com/in/janesmith",
|
|
98
|
+
"github": "github.com/janesmith",
|
|
99
|
+
"bio": "Building developer tools. Previously at Stripe.",
|
|
98
100
|
"facts": {
|
|
99
|
-
"x handle": "@
|
|
100
|
-
"telegram": "@
|
|
101
|
+
"x handle": "@janesmith",
|
|
102
|
+
"telegram": "@jane"
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
```
|
|
104
106
|
|
|
105
|
-
### Business persona (`businesses/
|
|
107
|
+
### Business persona (`businesses/acme-labs.json`)
|
|
106
108
|
|
|
107
109
|
```json
|
|
108
110
|
{
|
|
109
|
-
"name": "
|
|
110
|
-
"oneLiner": "
|
|
111
|
-
"website": "
|
|
112
|
-
"problem": "
|
|
113
|
-
"solution": "
|
|
111
|
+
"name": "Acme Labs",
|
|
112
|
+
"oneLiner": "AI-powered form automation",
|
|
113
|
+
"website": "acmelabs.dev",
|
|
114
|
+
"problem": "Filling out repetitive applications wastes hours",
|
|
115
|
+
"solution": "Smart persona-based form filling"
|
|
114
116
|
}
|
|
115
117
|
```
|
|
116
118
|
|
|
117
|
-
### Form persona (`forms/
|
|
119
|
+
### Form persona (`forms/yc-application.json`)
|
|
118
120
|
|
|
119
121
|
```json
|
|
120
122
|
{
|
|
121
|
-
"name": "
|
|
122
|
-
"urls": ["
|
|
123
|
-
"organization": "
|
|
124
|
-
"purpose": "
|
|
125
|
-
"notes": "
|
|
123
|
+
"name": "YC Application",
|
|
124
|
+
"urls": ["apply.ycombinator.com"],
|
|
125
|
+
"organization": "Y Combinator",
|
|
126
|
+
"purpose": "Startup accelerator application",
|
|
127
|
+
"notes": "3-month program in SF, $500k investment",
|
|
126
128
|
"deadline": "2026-04-01",
|
|
127
129
|
"facts": {
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"last week": "Shipped v2 of the API, onboarded 3 beta customers"
|
|
130
|
+
"why this accelerator": "The alumni network and partner expertise in developer tools",
|
|
131
|
+
"spend": "Engineering hires and go-to-market",
|
|
132
|
+
"last week": "Launched beta, onboarded 50 users, 30% week-over-week growth"
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
```
|
|
135
136
|
|
|
136
137
|
The `facts` on a form persona are form-specific answers keyed by label hints. They override user/business data when a form field matches.
|
|
137
138
|
|
|
139
|
+
## Twitter import (x402)
|
|
140
|
+
|
|
141
|
+
Pull your Twitter profile into a user persona with a single command. Uses the [x402 protocol](https://x402.org/) for pay-per-call access ($0.01 USDC on Base).
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Set your Base wallet private key
|
|
145
|
+
export PRIVATE_KEY=0x...
|
|
146
|
+
|
|
147
|
+
# Import your Twitter profile
|
|
148
|
+
npx @1dolinski/fastforms import twitter janesmith
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This creates `users/janesmith.json` pre-filled with your name, bio, location, and handle from Twitter. You can then `fastforms edit` to add more details.
|
|
152
|
+
|
|
153
|
+
Requires a Base network wallet with USDC. Powered by [APINow.fun](https://apinow.fun).
|
|
154
|
+
|
|
138
155
|
## Web app (optional)
|
|
139
156
|
|
|
140
157
|
You can also manage personas in the web UI at [293-fastforms.vercel.app/persona](https://293-fastforms.vercel.app/persona) and use `--web` flag to pull from there. The web app has an "Export for CLI" button to download your personas as local JSON files.
|
package/SKILL.md
CHANGED
|
@@ -12,6 +12,7 @@ Use this skill when the user says any of:
|
|
|
12
12
|
- "use fastforms", "fastforms fill"
|
|
13
13
|
- "set up my personas", "init fastforms"
|
|
14
14
|
- "add a persona", "add a form persona"
|
|
15
|
+
- "import from twitter", "pull my twitter profile"
|
|
15
16
|
|
|
16
17
|
## What it does
|
|
17
18
|
|
|
@@ -21,17 +22,22 @@ Use this skill when the user says any of:
|
|
|
21
22
|
- **User** — who you are (name, email, bio, skills)
|
|
22
23
|
- **Business** — what you're building (company, product, traction)
|
|
23
24
|
- **Form** — who's asking and why (org, purpose, form-specific answers)
|
|
24
|
-
2.
|
|
25
|
-
3.
|
|
26
|
-
4.
|
|
25
|
+
2. Can import user personas from Twitter via x402 micropayment
|
|
26
|
+
3. Connects to Chrome via the DevTools Protocol
|
|
27
|
+
4. Lets you pick which user + business + form persona to use at fill time
|
|
28
|
+
5. Fills any form using label-matching — never submits
|
|
27
29
|
|
|
28
30
|
## Quick start
|
|
29
31
|
|
|
30
32
|
```bash
|
|
31
|
-
# 1. Create your first
|
|
33
|
+
# 1. Create your first personas
|
|
32
34
|
npx @1dolinski/fastforms init
|
|
33
35
|
|
|
34
|
-
#
|
|
36
|
+
# Or import from Twitter ($0.01 USDC on Base)
|
|
37
|
+
export PRIVATE_KEY=0x...
|
|
38
|
+
npx @1dolinski/fastforms import twitter <your-handle>
|
|
39
|
+
|
|
40
|
+
# 2. Add form-specific context
|
|
35
41
|
npx @1dolinski/fastforms add form
|
|
36
42
|
|
|
37
43
|
# 3. Enable remote debugging in Chrome
|
|
@@ -47,6 +53,10 @@ npx @1dolinski/fastforms fill https://example.com/apply
|
|
|
47
53
|
|
|
48
54
|
Walks through creating a user + business persona (optionally a form persona too).
|
|
49
55
|
|
|
56
|
+
### `npx @1dolinski/fastforms import twitter <username>`
|
|
57
|
+
|
|
58
|
+
Imports a user persona from Twitter via x402 micropayment ($0.01 USDC on Base). Requires `PRIVATE_KEY` env var set to a Base wallet private key.
|
|
59
|
+
|
|
50
60
|
### `npx @1dolinski/fastforms add user|business|form`
|
|
51
61
|
|
|
52
62
|
Add another persona of any type.
|
|
@@ -84,9 +94,9 @@ Who you are. Name, email, role, GitHub, LinkedIn, bio, custom facts.
|
|
|
84
94
|
What you're building. Company, product, one-liner, traction, business model.
|
|
85
95
|
|
|
86
96
|
### Form persona
|
|
87
|
-
Who's asking and why. The organization that owns the form, the form's purpose, and **form-specific answers** that override user/business data.
|
|
97
|
+
Who's asking and why. The organization that owns the form, the form's purpose, and **form-specific answers** that override user/business data.
|
|
88
98
|
|
|
89
|
-
Form personas auto-match by URL. If your form persona has `urls: ["
|
|
99
|
+
Form personas auto-match by URL. If your form persona has `urls: ["apply.ycombinator.com"]` and you fill that URL, it auto-selects.
|
|
90
100
|
|
|
91
101
|
## How it works
|
|
92
102
|
|
|
@@ -108,6 +118,12 @@ When the user asks you to fill a form:
|
|
|
108
118
|
4. If Chrome debugging isn't enabled, tell the user to open `chrome://inspect/#remote-debugging`
|
|
109
119
|
5. After filling, tell the user to review in Chrome and submit manually
|
|
110
120
|
|
|
121
|
+
When the user wants to import from Twitter:
|
|
122
|
+
|
|
123
|
+
1. They need `PRIVATE_KEY` set (Base wallet with USDC)
|
|
124
|
+
2. Run `npx @1dolinski/fastforms import twitter <handle>`
|
|
125
|
+
3. The persona will be saved to `.fastforms/users/<handle>.json`
|
|
126
|
+
|
|
111
127
|
When the user wants to add a persona:
|
|
112
128
|
|
|
113
129
|
1. Run `npx @1dolinski/fastforms add user|business|form`
|
package/bin/fastforms.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { createInterface } from "readline";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
6
|
-
import { existsSync } from "fs";
|
|
6
|
+
import { existsSync, readFileSync } from "fs";
|
|
7
7
|
|
|
8
8
|
import { ensureChromeReady } from "../lib/chrome.js";
|
|
9
9
|
import {
|
|
@@ -32,6 +32,17 @@ import {
|
|
|
32
32
|
formTemplate,
|
|
33
33
|
} from "../lib/local.js";
|
|
34
34
|
|
|
35
|
+
// Load .env files for PRIVATE_KEY (used by x402 twitter import)
|
|
36
|
+
function loadEnvFile(path) {
|
|
37
|
+
if (!existsSync(path)) return;
|
|
38
|
+
for (const line of readFileSync(path, "utf-8").split("\n")) {
|
|
39
|
+
const match = line.match(/^\s*([\w]+)\s*=\s*(.+?)\s*$/);
|
|
40
|
+
if (match && !process.env[match[1]]) process.env[match[1]] = match[2];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
loadEnvFile(join(process.cwd(), ".fastforms", ".env"));
|
|
44
|
+
loadEnvFile(join(process.cwd(), ".env"));
|
|
45
|
+
|
|
35
46
|
const args = process.argv.slice(2);
|
|
36
47
|
const command = args[0];
|
|
37
48
|
|
|
@@ -69,16 +80,14 @@ function help() {
|
|
|
69
80
|
fastforms — Fill any form fast, with multiple personas.
|
|
70
81
|
|
|
71
82
|
Usage:
|
|
72
|
-
fastforms init
|
|
73
|
-
fastforms
|
|
74
|
-
fastforms add business
|
|
75
|
-
fastforms
|
|
76
|
-
fastforms
|
|
77
|
-
fastforms
|
|
78
|
-
fastforms
|
|
79
|
-
fastforms
|
|
80
|
-
fastforms personas Open web persona manager in Chrome
|
|
81
|
-
fastforms Show this help
|
|
83
|
+
fastforms init Create your first user + business persona
|
|
84
|
+
fastforms import twitter <handle> Import user persona from Twitter (x402)
|
|
85
|
+
fastforms add user|business|form Add another persona
|
|
86
|
+
fastforms list List all personas
|
|
87
|
+
fastforms edit Edit an existing persona
|
|
88
|
+
fastforms remove Remove a persona
|
|
89
|
+
fastforms fill <url> Fill a form (pick personas interactively)
|
|
90
|
+
fastforms personas Open web persona manager in Chrome
|
|
82
91
|
|
|
83
92
|
Options:
|
|
84
93
|
--web Use web app personas instead of local .fastforms/
|
|
@@ -93,9 +102,13 @@ function help() {
|
|
|
93
102
|
business — what you're building (company, product, traction)
|
|
94
103
|
form — who's asking and why (org, purpose, form-specific answers)
|
|
95
104
|
|
|
105
|
+
Twitter import:
|
|
106
|
+
Requires PRIVATE_KEY env var (Base wallet with USDC, $0.01/call).
|
|
107
|
+
Set in .env, .fastforms/.env, or export directly.
|
|
108
|
+
|
|
96
109
|
Quick start:
|
|
97
110
|
1. npx @1dolinski/fastforms init
|
|
98
|
-
2. npx @1dolinski/fastforms add form
|
|
111
|
+
2. npx @1dolinski/fastforms add form
|
|
99
112
|
3. Enable remote debugging: chrome://inspect/#remote-debugging
|
|
100
113
|
4. npx @1dolinski/fastforms fill https://example.com/apply
|
|
101
114
|
`);
|
|
@@ -605,6 +618,52 @@ async function openPersonas() {
|
|
|
605
618
|
browser.disconnect();
|
|
606
619
|
}
|
|
607
620
|
|
|
621
|
+
// ---------------------------------------------------------------------------
|
|
622
|
+
// import — pull persona from external source
|
|
623
|
+
// ---------------------------------------------------------------------------
|
|
624
|
+
|
|
625
|
+
async function importPersona() {
|
|
626
|
+
const source = args[1];
|
|
627
|
+
const handle = args[2];
|
|
628
|
+
|
|
629
|
+
if (source !== "twitter" || !handle) {
|
|
630
|
+
console.error(" Usage: fastforms import twitter <handle>\n");
|
|
631
|
+
console.error(" Requires PRIVATE_KEY env var (Base wallet with USDC).");
|
|
632
|
+
console.error(" Set in .env, .fastforms/.env, or: export PRIVATE_KEY=0x...\n");
|
|
633
|
+
process.exit(1);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
console.log(`\n fastforms import — Twitter → user persona\n`);
|
|
637
|
+
|
|
638
|
+
let fetchTwitterProfile, twitterToPersona;
|
|
639
|
+
try {
|
|
640
|
+
({ fetchTwitterProfile, twitterToPersona } = await import("../lib/x402.js"));
|
|
641
|
+
} catch (e) {
|
|
642
|
+
console.error(" Failed to load x402 module:", e.message);
|
|
643
|
+
console.error("\n Make sure dependencies are installed:");
|
|
644
|
+
console.error(" npm install viem @x402/fetch @x402/evm\n");
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const data = await fetchTwitterProfile(handle);
|
|
649
|
+
const persona = twitterToPersona(data, handle);
|
|
650
|
+
|
|
651
|
+
console.log(`\n Twitter profile for @${handle}:`);
|
|
652
|
+
if (persona.fullName) console.log(` Name: ${persona.fullName}`);
|
|
653
|
+
if (persona.bio) console.log(` Bio: ${persona.bio.slice(0, 80)}${persona.bio.length > 80 ? "..." : ""}`);
|
|
654
|
+
if (persona.location) console.log(` Location: ${persona.location}`);
|
|
655
|
+
if (persona.portfolio) console.log(` Website: ${persona.portfolio}`);
|
|
656
|
+
const factCount = Object.keys(persona.facts || {}).length;
|
|
657
|
+
if (factCount) console.log(` Facts: ${factCount}`);
|
|
658
|
+
|
|
659
|
+
const dir = resolveDir();
|
|
660
|
+
ensureDir(dir);
|
|
661
|
+
const slug = saveUserPersona(dir, persona);
|
|
662
|
+
console.log(`\n Saved to ${dir}/users/${slug}.json`);
|
|
663
|
+
console.log(" Run 'fastforms edit' to add email, role, and other details.\n");
|
|
664
|
+
closeRL();
|
|
665
|
+
}
|
|
666
|
+
|
|
608
667
|
// ---------------------------------------------------------------------------
|
|
609
668
|
// Route
|
|
610
669
|
// ---------------------------------------------------------------------------
|
|
@@ -613,6 +672,9 @@ switch (command) {
|
|
|
613
672
|
case "init":
|
|
614
673
|
init().catch((e) => { console.error(e.message); process.exit(1); });
|
|
615
674
|
break;
|
|
675
|
+
case "import":
|
|
676
|
+
importPersona().catch((e) => { console.error(e.message); process.exit(1); });
|
|
677
|
+
break;
|
|
616
678
|
case "add":
|
|
617
679
|
addPersona().catch((e) => { console.error(e.message); process.exit(1); });
|
|
618
680
|
break;
|
package/lib/x402.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const APINOW_BASE = "https://www.apinow.fun/api/v1";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fetches a Twitter user profile via APINow x402 API.
|
|
5
|
+
* Requires PRIVATE_KEY env var (Base network wallet with USDC).
|
|
6
|
+
* Costs $0.01 per call.
|
|
7
|
+
*/
|
|
8
|
+
export async function fetchTwitterProfile(username) {
|
|
9
|
+
const privateKey = process.env.PRIVATE_KEY;
|
|
10
|
+
if (!privateKey) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"PRIVATE_KEY not set.\n\n" +
|
|
13
|
+
" Twitter import uses x402 micropayments ($0.01 USDC on Base).\n" +
|
|
14
|
+
" Set your Base wallet private key:\n\n" +
|
|
15
|
+
" export PRIVATE_KEY=0x...\n\n" +
|
|
16
|
+
" Or add it to .fastforms/.env or .env"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
21
|
+
const { createWalletClient, createPublicClient, http } = await import("viem");
|
|
22
|
+
const { base } = await import("viem/chains");
|
|
23
|
+
const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
|
|
24
|
+
const { ExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
25
|
+
const { toClientEvmSigner } = await import("@x402/evm");
|
|
26
|
+
|
|
27
|
+
const account = privateKeyToAccount(privateKey);
|
|
28
|
+
|
|
29
|
+
const walletClient = createWalletClient({
|
|
30
|
+
account,
|
|
31
|
+
chain: base,
|
|
32
|
+
transport: http("https://mainnet.base.org"),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const publicClient = createPublicClient({
|
|
36
|
+
chain: base,
|
|
37
|
+
transport: http("https://mainnet.base.org"),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const signer = toClientEvmSigner(
|
|
41
|
+
{
|
|
42
|
+
address: account.address,
|
|
43
|
+
signTypedData: (args) => walletClient.signTypedData(args),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
readContract: (args) => publicClient.readContract(args),
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const client = new x402Client();
|
|
51
|
+
const scheme = new ExactEvmScheme(signer);
|
|
52
|
+
client.register("eip155:8453", scheme);
|
|
53
|
+
|
|
54
|
+
const paidFetch = wrapFetchWithPayment(globalThis.fetch, client);
|
|
55
|
+
|
|
56
|
+
const url = `${APINOW_BASE}/twit/getUserByUsername?username=${encodeURIComponent(username)}`;
|
|
57
|
+
|
|
58
|
+
console.log(` Fetching @${username} via x402 ($0.01 USDC on Base)...`);
|
|
59
|
+
console.log(` Wallet: ${account.address}`);
|
|
60
|
+
|
|
61
|
+
const res = await paidFetch(url, {
|
|
62
|
+
headers: { "x-wallet-address": account.address },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const body = await res.text().catch(() => "");
|
|
67
|
+
throw new Error(`Twitter API returned ${res.status}: ${body.slice(0, 200)}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = await res.json();
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Maps raw Twitter API response to a fastforms user persona.
|
|
76
|
+
*/
|
|
77
|
+
export function twitterToPersona(data, username) {
|
|
78
|
+
const user = data?.data || data?.user || data;
|
|
79
|
+
|
|
80
|
+
const name = user.username || username;
|
|
81
|
+
const fullName = user.name || "";
|
|
82
|
+
const bio = user.description || "";
|
|
83
|
+
const location = user.location || "";
|
|
84
|
+
|
|
85
|
+
let website = "";
|
|
86
|
+
if (user.entities?.url?.urls?.[0]?.expanded_url) {
|
|
87
|
+
website = user.entities.url.urls[0].expanded_url;
|
|
88
|
+
} else if (user.url) {
|
|
89
|
+
website = user.url;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const metrics = user.public_metrics || {};
|
|
93
|
+
|
|
94
|
+
const facts = {};
|
|
95
|
+
facts["x handle"] = `@${name}`;
|
|
96
|
+
if (metrics.followers_count) facts["twitter followers"] = String(metrics.followers_count);
|
|
97
|
+
if (metrics.tweet_count) facts["tweet count"] = String(metrics.tweet_count);
|
|
98
|
+
if (user.profile_image_url) facts["avatar"] = user.profile_image_url.replace("_normal", "");
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
name,
|
|
102
|
+
fullName,
|
|
103
|
+
email: "",
|
|
104
|
+
role: "",
|
|
105
|
+
location,
|
|
106
|
+
linkedIn: "",
|
|
107
|
+
github: "",
|
|
108
|
+
bio,
|
|
109
|
+
portfolio: website,
|
|
110
|
+
facts,
|
|
111
|
+
};
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1dolinski/fastforms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Fill any form fast. Manage personas on the web, fill forms from your terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
+
"@x402/evm": "^2.6.0",
|
|
17
|
+
"@x402/fetch": "^2.6.0",
|
|
18
|
+
"viem": "^2.47.4",
|
|
16
19
|
"ws": "^8.18.0"
|
|
17
20
|
},
|
|
18
21
|
"engines": {
|
|
@@ -28,6 +31,8 @@
|
|
|
28
31
|
"automation",
|
|
29
32
|
"chrome",
|
|
30
33
|
"cdp",
|
|
31
|
-
"personas"
|
|
34
|
+
"personas",
|
|
35
|
+
"x402",
|
|
36
|
+
"twitter"
|
|
32
37
|
]
|
|
33
38
|
}
|