@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 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. Add a form persona (org context + form-specific answers)
12
- npx @1dolinski/fastforms add form
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 add form`** captures the form's org, purpose, and form-specific answers
25
- 3. Personas are saved as individual JSON files in `.fastforms/`
26
- 4. **`fastforms fill <url>`** connects to Chrome, picks personas, fills by label matching
27
- 5. Form personas auto-match by URL no need to specify which one
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. "What attracts you to Nitro" belongs on the Nitro form persona, not on your user persona.
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
- chris.json
76
- work-chris.json
77
+ jane.json
78
+ work-jane.json
77
79
  businesses/
78
- apinow.json
79
- sideproject.json
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/chris.json`)
88
+ ### User persona (`users/jane.json`)
87
89
 
88
90
  ```json
89
91
  {
90
- "name": "chris",
91
- "fullName": "Chris Dolinski",
92
- "email": "chris@example.com",
93
- "role": "Founder",
94
- "location": "Toronto, ON",
95
- "linkedIn": "linkedin.com/in/1dolinski",
96
- "github": "github.com/1dolinski",
97
- "bio": "Serial entrepreneur",
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": "@1dolinski",
100
- "telegram": "@chris"
101
+ "x handle": "@janesmith",
102
+ "telegram": "@jane"
101
103
  }
102
104
  }
103
105
  ```
104
106
 
105
- ### Business persona (`businesses/apinow.json`)
107
+ ### Business persona (`businesses/acme-labs.json`)
106
108
 
107
109
  ```json
108
110
  {
109
- "name": "APINow.fun",
110
- "oneLiner": "x402 everything",
111
- "website": "apinow.fun",
112
- "problem": "Payments are broken",
113
- "solution": "Fix them with x402"
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/nitro-accelerator.json`)
119
+ ### Form persona (`forms/yc-application.json`)
118
120
 
119
121
  ```json
120
122
  {
121
- "name": "Nitro Accelerator",
122
- "urls": ["nitroacc.xyz"],
123
- "organization": "Nitro",
124
- "purpose": "Crypto accelerator application for early-stage founders",
125
- "notes": "NYC-based, 1-month residency",
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
- "attracts": "The density of high-signal founders and the NYC residency",
129
- "mentor": "Vitalik his work on public goods aligns with our mission",
130
- "spend": "Engineering hires and infrastructure for mainnet launch",
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. Connects to Chrome via the DevTools Protocol
25
- 3. Lets you pick which user + business + form persona to use at fill time
26
- 4. Fills any form using label-matching never submits
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 user + business persona
33
+ # 1. Create your first personas
32
34
  npx @1dolinski/fastforms init
33
35
 
34
- # 2. Add a form persona with org context and form-specific answers
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. For example, "what attracts you to Nitro" is a Nitro-specific answer — it belongs on the form persona, not on your user or business persona.
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: ["nitroacc.xyz"]` and you fill `https://nitroacc.xyz/apply`, it auto-selects.
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 Create your first user + business persona
73
- fastforms add user Add another user persona
74
- fastforms add business Add another business persona
75
- fastforms add form Add a form persona (org + purpose + answers)
76
- fastforms list List all personas
77
- fastforms edit Edit an existing persona
78
- fastforms remove Remove a persona
79
- fastforms fill <url> Fill a form (pick personas interactively)
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 # add form-specific context
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.4.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
  }