@1dolinski/fastforms 0.1.2 → 0.2.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 +28 -16
- package/SKILL.md +42 -13
- package/bin/fastforms.js +267 -150
- package/lib/local.js +78 -17
- package/lib/personas.js +52 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
# fastforms
|
|
2
2
|
|
|
3
|
-
Fill any form fast. Manage personas locally,
|
|
3
|
+
Fill any form fast. Manage multiple personas locally, pick the right one at fill time.
|
|
4
4
|
|
|
5
5
|
## Quick start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
# 1. Create your
|
|
8
|
+
# 1. Create your first user + business persona
|
|
9
9
|
npx @1dolinski/fastforms init
|
|
10
10
|
|
|
11
|
-
# 2.
|
|
11
|
+
# 2. Add more personas
|
|
12
|
+
npx @1dolinski/fastforms add user
|
|
13
|
+
npx @1dolinski/fastforms add business
|
|
14
|
+
|
|
15
|
+
# 3. Enable remote debugging in Chrome
|
|
12
16
|
# Open chrome://inspect/#remote-debugging and toggle it on
|
|
13
17
|
|
|
14
|
-
#
|
|
18
|
+
# 4. Fill any form — select which personas to use
|
|
15
19
|
npx @1dolinski/fastforms fill https://example.com/apply
|
|
16
20
|
```
|
|
17
21
|
|
|
18
22
|
## How it works
|
|
19
23
|
|
|
20
24
|
1. **`fastforms init`** walks you through creating user + business personas interactively
|
|
21
|
-
2. Personas are saved as
|
|
22
|
-
3. **`fastforms fill <url>`** connects to Chrome,
|
|
25
|
+
2. Personas are saved as individual JSON files in `.fastforms/users/` and `.fastforms/businesses/`
|
|
26
|
+
3. **`fastforms fill <url>`** connects to Chrome, lets you pick personas, fills by label matching
|
|
23
27
|
4. **Review and submit manually** in Chrome
|
|
24
28
|
|
|
25
29
|
## Requirements
|
|
@@ -31,31 +35,39 @@ npx @1dolinski/fastforms fill https://example.com/apply
|
|
|
31
35
|
|
|
32
36
|
| Command | Description |
|
|
33
37
|
|---|---|
|
|
34
|
-
| `fastforms init` | Create
|
|
35
|
-
| `fastforms
|
|
36
|
-
| `fastforms
|
|
38
|
+
| `fastforms init` | Create your first user + business persona |
|
|
39
|
+
| `fastforms add user` | Add another user persona |
|
|
40
|
+
| `fastforms add business` | Add another business persona |
|
|
41
|
+
| `fastforms list` | Show all saved personas |
|
|
42
|
+
| `fastforms fill <url>` | Fill any form (pick from personas) |
|
|
43
|
+
| `fastforms edit` | Edit an existing persona |
|
|
44
|
+
| `fastforms remove` | Remove a persona |
|
|
37
45
|
| `fastforms personas` | Open web persona manager in Chrome |
|
|
38
46
|
|
|
39
47
|
### Fill options
|
|
40
48
|
|
|
41
49
|
| Option | Description |
|
|
42
50
|
|---|---|
|
|
51
|
+
| `--user <hint>` | Pre-select user persona by name |
|
|
52
|
+
| `--business <hint>` | Pre-select business persona by name |
|
|
43
53
|
| `--web` | Use web app personas instead of local files |
|
|
44
54
|
| `--dir <path>` | Custom persona directory path |
|
|
45
55
|
| `--port <port>` | Chrome debug port (auto-detected) |
|
|
46
|
-
| `--user <hint>` | User persona hint (web mode) |
|
|
47
|
-
| `--business <hint>` | Business persona hint (web mode) |
|
|
48
56
|
|
|
49
57
|
## `.fastforms/` directory
|
|
50
58
|
|
|
51
59
|
```
|
|
52
60
|
.fastforms/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
users/
|
|
62
|
+
chris.json # A user persona
|
|
63
|
+
work-chris.json # Another user persona
|
|
64
|
+
businesses/
|
|
65
|
+
apinow.json # A business persona
|
|
66
|
+
sideproject.json # Another business persona
|
|
67
|
+
defaults.json # Remembers your last selection
|
|
56
68
|
```
|
|
57
69
|
|
|
58
|
-
|
|
70
|
+
Each user JSON file — just fill in what you have:
|
|
59
71
|
|
|
60
72
|
```json
|
|
61
73
|
{
|
|
@@ -74,7 +86,7 @@ npx @1dolinski/fastforms fill https://example.com/apply
|
|
|
74
86
|
}
|
|
75
87
|
```
|
|
76
88
|
|
|
77
|
-
|
|
89
|
+
Each business JSON file:
|
|
78
90
|
|
|
79
91
|
```json
|
|
80
92
|
{
|
package/SKILL.md
CHANGED
|
@@ -11,25 +11,31 @@ Use this skill when the user says any of:
|
|
|
11
11
|
- "fill this form with my persona"
|
|
12
12
|
- "use fastforms", "fastforms fill"
|
|
13
13
|
- "set up my personas", "init fastforms"
|
|
14
|
+
- "add a persona", "add another persona"
|
|
14
15
|
|
|
15
16
|
## What it does
|
|
16
17
|
|
|
17
18
|
`fastforms` is a CLI tool that:
|
|
18
19
|
|
|
19
|
-
1. Manages personas locally in
|
|
20
|
+
1. Manages multiple personas locally in `.fastforms/users/` and `.fastforms/businesses/` directories
|
|
20
21
|
2. Connects to Chrome via the DevTools Protocol
|
|
21
|
-
3.
|
|
22
|
+
3. Lets you pick which user + business persona to use at fill time
|
|
23
|
+
4. Fills any form using label-matching — never submits
|
|
22
24
|
|
|
23
25
|
## Quick start
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
# 1. Create your
|
|
28
|
+
# 1. Create your first user + business persona
|
|
27
29
|
npx @1dolinski/fastforms init
|
|
28
30
|
|
|
29
|
-
# 2.
|
|
31
|
+
# 2. Add more personas
|
|
32
|
+
npx @1dolinski/fastforms add user
|
|
33
|
+
npx @1dolinski/fastforms add business
|
|
34
|
+
|
|
35
|
+
# 3. Enable remote debugging in Chrome
|
|
30
36
|
# Open chrome://inspect/#remote-debugging
|
|
31
37
|
|
|
32
|
-
#
|
|
38
|
+
# 4. Fill any form — pick from your personas
|
|
33
39
|
npx @1dolinski/fastforms fill https://example.com/apply
|
|
34
40
|
```
|
|
35
41
|
|
|
@@ -37,20 +43,34 @@ npx @1dolinski/fastforms fill https://example.com/apply
|
|
|
37
43
|
|
|
38
44
|
### `npx @1dolinski/fastforms init`
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
Walks through creating a user + business persona. Saves to `.fastforms/users/<name>.json` and `.fastforms/businesses/<name>.json`.
|
|
47
|
+
|
|
48
|
+
### `npx @1dolinski/fastforms add user|business`
|
|
49
|
+
|
|
50
|
+
Add another user or business persona. Run as many times as you want.
|
|
51
|
+
|
|
52
|
+
### `npx @1dolinski/fastforms list`
|
|
53
|
+
|
|
54
|
+
Show all saved personas.
|
|
41
55
|
|
|
42
56
|
### `npx @1dolinski/fastforms fill <url>`
|
|
43
57
|
|
|
44
|
-
Fills any form.
|
|
58
|
+
Fills any form. If you have multiple personas, prompts you to pick which ones to use.
|
|
45
59
|
|
|
46
60
|
Options:
|
|
61
|
+
- `--user <hint>` — pre-select a user persona by name
|
|
62
|
+
- `--business <hint>` — pre-select a business persona by name
|
|
47
63
|
- `--web` — use web app personas (https://293-fastforms.vercel.app) instead of local files
|
|
48
64
|
- `--dir <path>` — custom path to persona directory
|
|
49
65
|
- `--port <port>` — Chrome debug port (auto-detected by default)
|
|
50
66
|
|
|
51
67
|
### `npx @1dolinski/fastforms edit`
|
|
52
68
|
|
|
53
|
-
|
|
69
|
+
Pick a persona to edit interactively.
|
|
70
|
+
|
|
71
|
+
### `npx @1dolinski/fastforms remove`
|
|
72
|
+
|
|
73
|
+
Pick a persona to delete.
|
|
54
74
|
|
|
55
75
|
### `npx @1dolinski/fastforms personas`
|
|
56
76
|
|
|
@@ -63,11 +83,12 @@ Opens the web persona manager in Chrome.
|
|
|
63
83
|
|
|
64
84
|
## How it works
|
|
65
85
|
|
|
66
|
-
1. Reads personas from `.fastforms/
|
|
67
|
-
2.
|
|
68
|
-
3.
|
|
69
|
-
4.
|
|
70
|
-
5.
|
|
86
|
+
1. Reads all personas from `.fastforms/users/` and `.fastforms/businesses/`
|
|
87
|
+
2. If multiple, prompts you to pick which user + business persona to use
|
|
88
|
+
3. Auto-discovers Chrome's debug port from `DevToolsActivePort`
|
|
89
|
+
4. Opens (or reuses) the target form URL tab
|
|
90
|
+
5. Fills using label-matching heuristics. Has site-specific mappings for known forms.
|
|
91
|
+
6. Shows what was filled, what was skipped. **Never submits.**
|
|
71
92
|
|
|
72
93
|
## Agent instructions
|
|
73
94
|
|
|
@@ -77,3 +98,11 @@ When the user asks you to fill a form:
|
|
|
77
98
|
2. Run `npx @1dolinski/fastforms fill <the-url>`
|
|
78
99
|
3. If Chrome debugging isn't enabled, tell the user to open `chrome://inspect/#remote-debugging`
|
|
79
100
|
4. After filling, tell the user to review in Chrome and submit manually
|
|
101
|
+
|
|
102
|
+
When the user wants to add a persona:
|
|
103
|
+
|
|
104
|
+
1. Run `npx @1dolinski/fastforms add user` or `npx @1dolinski/fastforms add business`
|
|
105
|
+
|
|
106
|
+
When the user wants to see their personas:
|
|
107
|
+
|
|
108
|
+
1. Run `npx @1dolinski/fastforms list`
|
package/bin/fastforms.js
CHANGED
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
loadLocalPersonas,
|
|
22
22
|
saveUserPersona,
|
|
23
23
|
saveBusinessPersona,
|
|
24
|
+
deletePersonaFile,
|
|
25
|
+
listPersonaFiles,
|
|
26
|
+
loadDefaults,
|
|
27
|
+
saveDefaults,
|
|
24
28
|
userTemplate,
|
|
25
29
|
businessTemplate,
|
|
26
30
|
} from "../lib/local.js";
|
|
@@ -52,215 +56,315 @@ function ask(prompt, fallback = "") {
|
|
|
52
56
|
return new Promise((r) => getRL().question(prompt, (a) => r(a.trim() || fallback)));
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
function resolveDir() {
|
|
60
|
+
const dirArg = flag("--dir");
|
|
61
|
+
return dirArg || findFastformsDir() || join(process.cwd(), ".fastforms");
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
function help() {
|
|
56
65
|
console.log(`
|
|
57
|
-
fastforms — Fill any form fast.
|
|
66
|
+
fastforms — Fill any form fast, with multiple personas.
|
|
58
67
|
|
|
59
68
|
Usage:
|
|
60
|
-
fastforms init
|
|
61
|
-
fastforms
|
|
62
|
-
fastforms
|
|
69
|
+
fastforms init Create your first user + business persona
|
|
70
|
+
fastforms add user Add another user persona
|
|
71
|
+
fastforms add business Add another business persona
|
|
72
|
+
fastforms list List all personas
|
|
73
|
+
fastforms edit Edit an existing persona
|
|
74
|
+
fastforms remove Remove a persona
|
|
75
|
+
fastforms fill <url> Fill a form (pick personas interactively)
|
|
63
76
|
fastforms personas Open web persona manager in Chrome
|
|
64
77
|
fastforms Show this help
|
|
65
78
|
|
|
66
79
|
Options:
|
|
67
80
|
--web Use web app personas instead of local .fastforms/
|
|
68
81
|
--dir <path> Path to .fastforms/ directory (default: auto-detect)
|
|
69
|
-
--user <hint> User persona name/hint
|
|
70
|
-
--business <hint> Business persona name/hint
|
|
82
|
+
--user <hint> User persona name/hint to pre-select
|
|
83
|
+
--business <hint> Business persona name/hint to pre-select
|
|
71
84
|
--port <port> Chrome debug port (auto-detected by default)
|
|
72
85
|
|
|
73
86
|
Quick start:
|
|
74
|
-
1. npx fastforms init
|
|
75
|
-
2.
|
|
76
|
-
3.
|
|
87
|
+
1. npx @1dolinski/fastforms init
|
|
88
|
+
2. npx @1dolinski/fastforms add user # add more personas
|
|
89
|
+
3. Enable remote debugging: chrome://inspect/#remote-debugging
|
|
90
|
+
4. npx @1dolinski/fastforms fill https://example.com/apply
|
|
77
91
|
`);
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
// ---------------------------------------------------------------------------
|
|
81
|
-
//
|
|
95
|
+
// Shared persona builder prompts
|
|
82
96
|
// ---------------------------------------------------------------------------
|
|
83
97
|
|
|
84
|
-
async function
|
|
85
|
-
const
|
|
86
|
-
const
|
|
98
|
+
async function promptUser(existing) {
|
|
99
|
+
const ep = existing?.profile || {};
|
|
100
|
+
const u = userTemplate();
|
|
87
101
|
|
|
88
|
-
|
|
102
|
+
const show = (val) => val ? ` [${String(val).slice(0, 40)}]` : "";
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
u.name = await ask(` Name (identifier)${show(existing?.name)}: `, existing?.name || "");
|
|
105
|
+
u.fullName = await ask(` Full name${show(ep.fullName)}: `, ep.fullName || "");
|
|
106
|
+
u.email = await ask(` Email${show(ep.email)}: `, ep.email || "");
|
|
107
|
+
u.role = await ask(` Role / title${show(ep.currentRole)}: `, ep.currentRole || "");
|
|
108
|
+
u.location = await ask(` Location${show(ep.location)}: `, ep.location || "");
|
|
109
|
+
u.linkedIn = await ask(` LinkedIn${show(ep.linkedIn)}: `, ep.linkedIn || "");
|
|
110
|
+
u.github = await ask(` GitHub${show(ep.github)}: `, ep.github || "");
|
|
111
|
+
u.bio = await ask(` Short bio${show(ep.bio)}: `, ep.bio || "");
|
|
112
|
+
|
|
113
|
+
const existingFacts = {};
|
|
114
|
+
for (const f of (existing?.customFacts || [])) {
|
|
115
|
+
if (f.enabled !== false && f.key) existingFacts[f.key] = f.value;
|
|
96
116
|
}
|
|
117
|
+
u.facts = { ...existingFacts };
|
|
97
118
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
user.fullName = await ask(" Full name: ");
|
|
104
|
-
user.email = await ask(" Email: ");
|
|
105
|
-
user.role = await ask(" Role / title: ");
|
|
106
|
-
user.location = await ask(" Location: ");
|
|
107
|
-
user.linkedIn = await ask(" LinkedIn (optional): ");
|
|
108
|
-
user.github = await ask(" GitHub (optional): ");
|
|
109
|
-
user.bio = await ask(" Short bio (optional): ");
|
|
110
|
-
|
|
111
|
-
// Custom facts
|
|
112
|
-
console.log("\n Add custom facts (e.g. 'x handle = @1dolinski'). Press Enter to skip.\n");
|
|
119
|
+
if (Object.keys(u.facts).length) {
|
|
120
|
+
console.log("\n Current facts:");
|
|
121
|
+
for (const [k, v] of Object.entries(u.facts)) console.log(` ${k} = ${v}`);
|
|
122
|
+
}
|
|
123
|
+
console.log("\n Add custom facts (key = value). Press Enter to finish.\n");
|
|
113
124
|
while (true) {
|
|
114
125
|
const raw = await ask(" fact: ");
|
|
115
126
|
if (!raw) break;
|
|
116
127
|
const eq = raw.indexOf("=");
|
|
117
|
-
if (eq === -1) {
|
|
118
|
-
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
const key = raw.slice(0, eq).trim();
|
|
122
|
-
const value = raw.slice(eq + 1).trim();
|
|
123
|
-
if (key) user.facts[key] = value;
|
|
128
|
+
if (eq === -1) { console.log(" Use format: key = value"); continue; }
|
|
129
|
+
u.facts[raw.slice(0, eq).trim()] = raw.slice(eq + 1).trim();
|
|
124
130
|
}
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
132
|
+
return u;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function promptBusiness(existing) {
|
|
136
|
+
const bp = existing?.profile || {};
|
|
137
|
+
const b = businessTemplate();
|
|
138
|
+
|
|
139
|
+
const show = (val) => val ? ` [${String(val).slice(0, 40)}]` : "";
|
|
140
|
+
|
|
141
|
+
b.name = await ask(` Company / project name${show(existing?.name)}: `, existing?.name || "");
|
|
142
|
+
b.oneLiner = await ask(` One-liner${show(bp.oneLiner)}: `, bp.oneLiner || "");
|
|
143
|
+
b.website = await ask(` Website${show(bp.website)}: `, bp.website || "");
|
|
144
|
+
b.category = await ask(` Category${show(bp.category)}: `, bp.category || "");
|
|
145
|
+
b.location = await ask(` Location${show(bp.location)}: `, bp.location || "");
|
|
146
|
+
b.problem = await ask(` Problem you're solving${show(bp.problem)}: `, bp.problem || "");
|
|
147
|
+
b.solution = await ask(` Your solution${show(bp.solution)}: `, bp.solution || "");
|
|
148
|
+
b.targetUsers = await ask(` Target users${show(bp.targetUsers)}: `, bp.targetUsers || "");
|
|
149
|
+
b.traction = await ask(` Traction${show(bp.traction)}: `, bp.traction || "");
|
|
150
|
+
b.businessModel = await ask(` Business model${show(bp.businessModel)}: `, bp.businessModel || "");
|
|
151
|
+
b.differentiators = await ask(` Differentiators${show(bp.differentiators)}: `, bp.differentiators || "");
|
|
152
|
+
|
|
153
|
+
const existingFacts = {};
|
|
154
|
+
for (const f of (existing?.customFacts || [])) {
|
|
155
|
+
if (f.enabled !== false && f.key) existingFacts[f.key] = f.value;
|
|
156
|
+
}
|
|
157
|
+
b.facts = { ...existingFacts };
|
|
158
|
+
|
|
159
|
+
if (Object.keys(b.facts).length) {
|
|
160
|
+
console.log("\n Current facts:");
|
|
161
|
+
for (const [k, v] of Object.entries(b.facts)) console.log(` ${k} = ${v}`);
|
|
162
|
+
}
|
|
163
|
+
console.log("\n Add business facts (key = value). Press Enter to finish.\n");
|
|
143
164
|
while (true) {
|
|
144
165
|
const raw = await ask(" fact: ");
|
|
145
166
|
if (!raw) break;
|
|
146
167
|
const eq = raw.indexOf("=");
|
|
147
|
-
if (eq === -1) {
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
if (eq === -1) { console.log(" Use format: key = value"); continue; }
|
|
169
|
+
b.facts[raw.slice(0, eq).trim()] = raw.slice(eq + 1).trim();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return b;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// init — first-time setup: one user + one business
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
async function init() {
|
|
180
|
+
const dir = resolveDir();
|
|
181
|
+
|
|
182
|
+
console.log("\n fastforms — Let's set up your personas.\n");
|
|
183
|
+
|
|
184
|
+
const existingUsers = existsSync(join(dir, "users")) ? listPersonaFiles(dir, "user") : [];
|
|
185
|
+
if (existingUsers.length) {
|
|
186
|
+
const ans = await ask(` ${existingUsers.length} persona(s) already exist. Add another? [Y/n]: `);
|
|
187
|
+
if (ans.toLowerCase() === "n") {
|
|
188
|
+
console.log(" Use 'fastforms add user' or 'fastforms add business' to add more.\n");
|
|
189
|
+
closeRL();
|
|
190
|
+
return;
|
|
150
191
|
}
|
|
151
|
-
const key = raw.slice(0, eq).trim();
|
|
152
|
-
const value = raw.slice(eq + 1).trim();
|
|
153
|
-
if (key) biz.facts[key] = value;
|
|
154
192
|
}
|
|
155
193
|
|
|
156
|
-
|
|
194
|
+
console.log(" --- User persona ---\n");
|
|
195
|
+
const user = await promptUser();
|
|
157
196
|
ensureDir(dir);
|
|
158
|
-
saveUserPersona(dir, user);
|
|
159
|
-
|
|
197
|
+
const userSlug = saveUserPersona(dir, user);
|
|
198
|
+
console.log(`\n Saved users/${userSlug}.json`);
|
|
199
|
+
|
|
200
|
+
console.log("\n --- Business persona ---\n");
|
|
201
|
+
const biz = await promptBusiness();
|
|
202
|
+
const bizSlug = saveBusinessPersona(dir, biz);
|
|
203
|
+
console.log(`\n Saved businesses/${bizSlug}.json`);
|
|
160
204
|
|
|
161
|
-
console.log(`\n
|
|
162
|
-
console.log("
|
|
163
|
-
console.log(" business.json");
|
|
164
|
-
console.log("\n Next: npx fastforms fill <url>\n");
|
|
205
|
+
console.log(`\n Personas saved to ${dir}/`);
|
|
206
|
+
console.log(" Next: npx @1dolinski/fastforms fill <url>\n");
|
|
165
207
|
closeRL();
|
|
166
208
|
}
|
|
167
209
|
|
|
168
210
|
// ---------------------------------------------------------------------------
|
|
169
|
-
//
|
|
211
|
+
// add — add a single persona
|
|
170
212
|
// ---------------------------------------------------------------------------
|
|
171
213
|
|
|
172
|
-
async function
|
|
173
|
-
const
|
|
174
|
-
|
|
214
|
+
async function addPersona() {
|
|
215
|
+
const type = args[1];
|
|
216
|
+
if (type !== "user" && type !== "business") {
|
|
217
|
+
console.error(" Usage: fastforms add user|business\n");
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
175
220
|
|
|
221
|
+
const dir = resolveDir();
|
|
222
|
+
ensureDir(dir);
|
|
223
|
+
|
|
224
|
+
if (type === "user") {
|
|
225
|
+
console.log("\n --- New user persona ---\n");
|
|
226
|
+
const user = await promptUser();
|
|
227
|
+
const slug = saveUserPersona(dir, user);
|
|
228
|
+
console.log(`\n Saved users/${slug}.json to ${dir}/\n`);
|
|
229
|
+
} else {
|
|
230
|
+
console.log("\n --- New business persona ---\n");
|
|
231
|
+
const biz = await promptBusiness();
|
|
232
|
+
const slug = saveBusinessPersona(dir, biz);
|
|
233
|
+
console.log(`\n Saved businesses/${slug}.json to ${dir}/\n`);
|
|
234
|
+
}
|
|
235
|
+
closeRL();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// list — show all personas
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
function listAll() {
|
|
243
|
+
const dir = findFastformsDir();
|
|
176
244
|
if (!dir) {
|
|
177
245
|
console.error(" No .fastforms/ directory found. Run 'fastforms init' first.\n");
|
|
178
246
|
process.exit(1);
|
|
179
247
|
}
|
|
180
248
|
|
|
181
|
-
const
|
|
182
|
-
const existing = dump.personas[0];
|
|
183
|
-
const existingBiz = dump.businessPersonas[0];
|
|
249
|
+
const defaults = loadDefaults(dir);
|
|
184
250
|
|
|
185
|
-
|
|
251
|
+
const users = listPersonaFiles(dir, "user");
|
|
252
|
+
const businesses = listPersonaFiles(dir, "business");
|
|
186
253
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
user.linkedIn = await ask(` LinkedIn [${ep.linkedIn || ""}]: `, ep.linkedIn || "");
|
|
198
|
-
user.github = await ask(` GitHub [${ep.github || ""}]: `, ep.github || "");
|
|
199
|
-
user.bio = await ask(` Bio [${ep.bio ? ep.bio.slice(0, 40) + "..." : ""}]: `, ep.bio || "");
|
|
200
|
-
|
|
201
|
-
// Carry over existing facts
|
|
202
|
-
const existingFacts = {};
|
|
203
|
-
for (const f of (existing?.customFacts || [])) {
|
|
204
|
-
if (f.enabled !== false) existingFacts[f.key] = f.value;
|
|
254
|
+
console.log(`\n Personas in ${dir}/\n`);
|
|
255
|
+
|
|
256
|
+
if (users.length) {
|
|
257
|
+
console.log(" User personas:");
|
|
258
|
+
for (const u of users) {
|
|
259
|
+
const def = defaults.defaultUser === (u.data?.name || u.slug) ? " (default)" : "";
|
|
260
|
+
console.log(` ${u.slug}${def} — ${u.data?.fullName || u.data?.name || "?"} <${u.data?.email || "?"}>`);
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
console.log(" No user personas. Run: fastforms add user");
|
|
205
264
|
}
|
|
206
|
-
user.facts = { ...existingFacts };
|
|
207
265
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
266
|
+
console.log();
|
|
267
|
+
|
|
268
|
+
if (businesses.length) {
|
|
269
|
+
console.log(" Business personas:");
|
|
270
|
+
for (const b of businesses) {
|
|
271
|
+
const def = defaults.defaultBusiness === (b.data?.name || b.slug) ? " (default)" : "";
|
|
272
|
+
console.log(` ${b.slug}${def} — ${b.data?.name || "?"}: ${b.data?.oneLiner || ""}`);
|
|
212
273
|
}
|
|
274
|
+
} else {
|
|
275
|
+
console.log(" No business personas. Run: fastforms add business");
|
|
213
276
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
277
|
+
|
|
278
|
+
console.log();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// edit — pick a persona and edit it
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
async function edit() {
|
|
286
|
+
const dir = findFastformsDir();
|
|
287
|
+
if (!dir) {
|
|
288
|
+
console.error(" No .fastforms/ directory found. Run 'fastforms init' first.\n");
|
|
289
|
+
process.exit(1);
|
|
221
290
|
}
|
|
222
291
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
biz.problem = await ask(` Problem [${bp.problem ? bp.problem.slice(0, 40) + "..." : ""}]: `, bp.problem || "");
|
|
234
|
-
biz.solution = await ask(` Solution [${bp.solution ? bp.solution.slice(0, 40) + "..." : ""}]: `, bp.solution || "");
|
|
235
|
-
biz.targetUsers = await ask(` Target users [${bp.targetUsers ? bp.targetUsers.slice(0, 40) + "..." : ""}]: `, bp.targetUsers || "");
|
|
236
|
-
biz.traction = await ask(` Traction [${bp.traction ? bp.traction.slice(0, 40) + "..." : ""}]: `, bp.traction || "");
|
|
237
|
-
biz.businessModel = await ask(` Business model [${bp.businessModel ? bp.businessModel.slice(0, 40) + "..." : ""}]: `, bp.businessModel || "");
|
|
238
|
-
biz.differentiators = await ask(` Differentiators [${bp.differentiators ? bp.differentiators.slice(0, 40) + "..." : ""}]: `, bp.differentiators || "");
|
|
239
|
-
|
|
240
|
-
const existingBizFacts = {};
|
|
241
|
-
for (const f of (existingBiz?.customFacts || [])) {
|
|
242
|
-
if (f.enabled !== false) existingBizFacts[f.key] = f.value;
|
|
292
|
+
const users = listPersonaFiles(dir, "user");
|
|
293
|
+
const businesses = listPersonaFiles(dir, "business");
|
|
294
|
+
const all = [
|
|
295
|
+
...users.map((u) => ({ ...u, type: "user", label: `user: ${u.slug} (${u.data?.fullName || u.data?.name || "?"})` })),
|
|
296
|
+
...businesses.map((b) => ({ ...b, type: "business", label: `biz: ${b.slug} (${b.data?.name || "?"})` })),
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
if (!all.length) {
|
|
300
|
+
console.error(" No personas found. Run 'fastforms init' first.\n");
|
|
301
|
+
process.exit(1);
|
|
243
302
|
}
|
|
244
|
-
biz.facts = { ...existingBizFacts };
|
|
245
303
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
304
|
+
console.log("\n Which persona to edit?\n");
|
|
305
|
+
all.forEach((p, i) => console.log(` ${i + 1}. ${p.label}`));
|
|
306
|
+
const ans = await ask(`\n Pick [1-${all.length}]: `);
|
|
307
|
+
const idx = Number(ans) - 1;
|
|
308
|
+
if (idx < 0 || idx >= all.length) { console.log(" Invalid selection.\n"); closeRL(); return; }
|
|
309
|
+
|
|
310
|
+
const picked = all[idx];
|
|
311
|
+
const dump = loadLocalPersonas(dir);
|
|
312
|
+
|
|
313
|
+
if (picked.type === "user") {
|
|
314
|
+
const existing = dump.personas.find((p) => p.name === picked.data?.name) || null;
|
|
315
|
+
console.log(`\n --- Edit user: ${picked.slug} ---\n`);
|
|
316
|
+
const updated = await promptUser(existing);
|
|
317
|
+
if (updated.name !== picked.data?.name) deletePersonaFile(dir, "user", picked.slug);
|
|
318
|
+
const slug = saveUserPersona(dir, updated);
|
|
319
|
+
console.log(`\n Updated users/${slug}.json\n`);
|
|
320
|
+
} else {
|
|
321
|
+
const existing = dump.businessPersonas.find((p) => p.name === picked.data?.name) || null;
|
|
322
|
+
console.log(`\n --- Edit business: ${picked.slug} ---\n`);
|
|
323
|
+
const updated = await promptBusiness(existing);
|
|
324
|
+
if (updated.name !== picked.data?.name) deletePersonaFile(dir, "business", picked.slug);
|
|
325
|
+
const slug = saveBusinessPersona(dir, updated);
|
|
326
|
+
console.log(`\n Updated businesses/${slug}.json\n`);
|
|
251
327
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
328
|
+
closeRL();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
// remove — delete a persona
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
|
|
335
|
+
async function remove() {
|
|
336
|
+
const dir = findFastformsDir();
|
|
337
|
+
if (!dir) {
|
|
338
|
+
console.error(" No .fastforms/ directory found.\n");
|
|
339
|
+
process.exit(1);
|
|
259
340
|
}
|
|
260
341
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
342
|
+
const users = listPersonaFiles(dir, "user");
|
|
343
|
+
const businesses = listPersonaFiles(dir, "business");
|
|
344
|
+
const all = [
|
|
345
|
+
...users.map((u) => ({ ...u, type: "user", label: `user: ${u.slug} (${u.data?.fullName || u.data?.name || "?"})` })),
|
|
346
|
+
...businesses.map((b) => ({ ...b, type: "business", label: `biz: ${b.slug} (${b.data?.name || "?"})` })),
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
if (!all.length) {
|
|
350
|
+
console.error(" No personas to remove.\n");
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
console.log("\n Which persona to remove?\n");
|
|
355
|
+
all.forEach((p, i) => console.log(` ${i + 1}. ${p.label}`));
|
|
356
|
+
const ans = await ask(`\n Pick [1-${all.length}]: `);
|
|
357
|
+
const idx = Number(ans) - 1;
|
|
358
|
+
if (idx < 0 || idx >= all.length) { console.log(" Invalid selection.\n"); closeRL(); return; }
|
|
359
|
+
|
|
360
|
+
const picked = all[idx];
|
|
361
|
+
const confirm = await ask(` Delete ${picked.type}/${picked.slug}? [y/N]: `);
|
|
362
|
+
if (confirm.toLowerCase() === "y") {
|
|
363
|
+
deletePersonaFile(dir, picked.type, picked.slug);
|
|
364
|
+
console.log(` Removed ${picked.type}s/${picked.slug}.json\n`);
|
|
365
|
+
} else {
|
|
366
|
+
console.log(" Cancelled.\n");
|
|
367
|
+
}
|
|
264
368
|
closeRL();
|
|
265
369
|
}
|
|
266
370
|
|
|
@@ -285,14 +389,11 @@ async function fill() {
|
|
|
285
389
|
let browser;
|
|
286
390
|
|
|
287
391
|
if (hasFlag("--web")) {
|
|
288
|
-
// Web app mode
|
|
289
392
|
console.log(" Pulling personas from web app...");
|
|
290
393
|
browser = await connectToChrome(port);
|
|
291
394
|
dump = await pullPersonas(browser);
|
|
292
395
|
} else {
|
|
293
|
-
|
|
294
|
-
const dirArg = flag("--dir");
|
|
295
|
-
const dir = dirArg || findFastformsDir();
|
|
396
|
+
const dir = findFastformsDir();
|
|
296
397
|
|
|
297
398
|
if (!dir) {
|
|
298
399
|
console.error(" No .fastforms/ directory found.");
|
|
@@ -349,9 +450,16 @@ async function fill() {
|
|
|
349
450
|
|
|
350
451
|
if (hasFlag("--web")) {
|
|
351
452
|
await offerSetDefaults(user, biz);
|
|
453
|
+
} else {
|
|
454
|
+
// Save defaults to local dir
|
|
455
|
+
const dir = findFastformsDir();
|
|
456
|
+
if (dir && user && biz) {
|
|
457
|
+
saveDefaults(dir, { defaultUser: user.name, defaultBusiness: biz.name });
|
|
458
|
+
}
|
|
352
459
|
}
|
|
353
460
|
|
|
354
461
|
browser.disconnect();
|
|
462
|
+
closeRL();
|
|
355
463
|
}
|
|
356
464
|
|
|
357
465
|
// ---------------------------------------------------------------------------
|
|
@@ -382,9 +490,18 @@ switch (command) {
|
|
|
382
490
|
case "init":
|
|
383
491
|
init().catch((e) => { console.error(e.message); process.exit(1); });
|
|
384
492
|
break;
|
|
493
|
+
case "add":
|
|
494
|
+
addPersona().catch((e) => { console.error(e.message); process.exit(1); });
|
|
495
|
+
break;
|
|
496
|
+
case "list":
|
|
497
|
+
listAll();
|
|
498
|
+
break;
|
|
385
499
|
case "edit":
|
|
386
500
|
edit().catch((e) => { console.error(e.message); process.exit(1); });
|
|
387
501
|
break;
|
|
502
|
+
case "remove":
|
|
503
|
+
remove().catch((e) => { console.error(e.message); process.exit(1); });
|
|
504
|
+
break;
|
|
388
505
|
case "fill":
|
|
389
506
|
fill().catch((e) => { console.error(e.message); process.exit(1); });
|
|
390
507
|
break;
|
package/lib/local.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from "fs";
|
|
2
|
+
import { join, basename } from "path";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
|
|
5
5
|
export function findFastformsDir() {
|
|
@@ -25,10 +25,15 @@ function writeJson(path, data) {
|
|
|
25
25
|
writeFileSync(path, JSON.stringify(data, null, 2) + "\n");
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
function slugify(name) {
|
|
29
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "default";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Normalize flat local format → shape fill.js expects:
|
|
34
|
+
// { name, profile: { ...fields }, customFacts: [...] }
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
32
37
|
function normalizeUser(raw) {
|
|
33
38
|
if (!raw) return null;
|
|
34
39
|
const { name, facts, ...rest } = raw;
|
|
@@ -92,29 +97,81 @@ function factsToArray(facts) {
|
|
|
92
97
|
}));
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Multi-persona loading: reads users/ and businesses/ subdirs.
|
|
102
|
+
// Falls back to legacy user.json / business.json for backward compat.
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
function loadDir(dir, normalizer) {
|
|
106
|
+
if (!existsSync(dir)) return [];
|
|
107
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json")).sort();
|
|
108
|
+
return files.map((f) => normalizer(readJson(join(dir, f)))).filter(Boolean);
|
|
109
|
+
}
|
|
101
110
|
|
|
102
|
-
|
|
103
|
-
const
|
|
111
|
+
export function loadLocalPersonas(dir) {
|
|
112
|
+
const usersDir = join(dir, "users");
|
|
113
|
+
const bizDir = join(dir, "businesses");
|
|
114
|
+
|
|
115
|
+
let personas = loadDir(usersDir, normalizeUser);
|
|
116
|
+
let businessPersonas = loadDir(bizDir, normalizeBusiness);
|
|
117
|
+
|
|
118
|
+
// Backward compat: if subdirs are empty, check legacy single files
|
|
119
|
+
if (!personas.length) {
|
|
120
|
+
const legacy = readJson(join(dir, "user.json"));
|
|
121
|
+
if (legacy) personas = [normalizeUser(legacy)];
|
|
122
|
+
}
|
|
123
|
+
if (!businessPersonas.length) {
|
|
124
|
+
const legacy = readJson(join(dir, "business.json"));
|
|
125
|
+
if (legacy) businessPersonas = [normalizeBusiness(legacy)];
|
|
126
|
+
}
|
|
104
127
|
|
|
105
128
|
return { personas, businessPersonas };
|
|
106
129
|
}
|
|
107
130
|
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Save personas into named files under users/ or businesses/
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
108
135
|
export function saveUserPersona(dir, data) {
|
|
109
|
-
ensureDir(dir);
|
|
110
|
-
|
|
136
|
+
const subdir = ensureDir(join(dir, "users"));
|
|
137
|
+
const slug = slugify(data.name || "default");
|
|
138
|
+
writeJson(join(subdir, `${slug}.json`), data);
|
|
139
|
+
return slug;
|
|
111
140
|
}
|
|
112
141
|
|
|
113
142
|
export function saveBusinessPersona(dir, data) {
|
|
114
|
-
ensureDir(dir);
|
|
115
|
-
|
|
143
|
+
const subdir = ensureDir(join(dir, "businesses"));
|
|
144
|
+
const slug = slugify(data.name || "default");
|
|
145
|
+
writeJson(join(subdir, `${slug}.json`), data);
|
|
146
|
+
return slug;
|
|
116
147
|
}
|
|
117
148
|
|
|
149
|
+
export function deletePersonaFile(dir, type, slug) {
|
|
150
|
+
const subdir = type === "user" ? "users" : "businesses";
|
|
151
|
+
const path = join(dir, subdir, `${slug}.json`);
|
|
152
|
+
if (existsSync(path)) unlinkSync(path);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// List raw persona files (for edit/delete commands)
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
export function listPersonaFiles(dir, type) {
|
|
160
|
+
const subdir = join(dir, type === "user" ? "users" : "businesses");
|
|
161
|
+
if (!existsSync(subdir)) return [];
|
|
162
|
+
return readdirSync(subdir)
|
|
163
|
+
.filter((f) => f.endsWith(".json"))
|
|
164
|
+
.sort()
|
|
165
|
+
.map((f) => {
|
|
166
|
+
const data = readJson(join(subdir, f));
|
|
167
|
+
return { slug: basename(f, ".json"), file: f, data };
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Defaults
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
118
175
|
export function loadDefaults(dir) {
|
|
119
176
|
return readJson(join(dir, "defaults.json")) || {};
|
|
120
177
|
}
|
|
@@ -124,6 +181,10 @@ export function saveDefaults(dir, patch) {
|
|
|
124
181
|
writeJson(join(dir, "defaults.json"), { ...current, ...patch });
|
|
125
182
|
}
|
|
126
183
|
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Templates
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
|
|
127
188
|
export function userTemplate() {
|
|
128
189
|
return {
|
|
129
190
|
name: "",
|
package/lib/personas.js
CHANGED
|
@@ -110,35 +110,64 @@ export function showPersonaDetails(user, biz) {
|
|
|
110
110
|
]);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const bizPersonas = dump.businessPersonas || [];
|
|
117
|
-
|
|
118
|
-
// Use defaults if no hint and defaults are saved
|
|
119
|
-
const effectiveUserHint = userHint || config.defaultUser || "";
|
|
120
|
-
const effectiveBizHint = bizHint || config.defaultBusiness || "";
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Interactive persona selection — supports multiple personas from any source
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
121
116
|
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
async function pickFromList(label, list, hint, keys, defaultName) {
|
|
118
|
+
if (!list?.length) return null;
|
|
119
|
+
if (list.length === 1) return list[0];
|
|
124
120
|
|
|
125
|
-
//
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const ans = await ask(`\n Pick user persona [1-${personas.length}]: `);
|
|
130
|
-
const idx = Number(ans) - 1;
|
|
131
|
-
if (idx >= 0 && idx < personas.length) user = personas[idx];
|
|
121
|
+
// Try hint-based match
|
|
122
|
+
if (hint) {
|
|
123
|
+
const match = pickPersona(list, hint, keys);
|
|
124
|
+
if (match) return match;
|
|
132
125
|
}
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const idx = Number(ans) - 1;
|
|
139
|
-
if (idx >= 0 && idx < bizPersonas.length) biz = bizPersonas[idx];
|
|
127
|
+
// Try default
|
|
128
|
+
if (defaultName) {
|
|
129
|
+
const match = list.find((p) => p.name === defaultName);
|
|
130
|
+
if (match) return match;
|
|
140
131
|
}
|
|
141
132
|
|
|
133
|
+
// Interactive selection
|
|
134
|
+
console.log(`\n ${label}:\n`);
|
|
135
|
+
list.forEach((p, i) => {
|
|
136
|
+
const detail = keys.map((k) => k.split(".").reduce((o, s) => o?.[s], p)).filter(Boolean)[0] || "";
|
|
137
|
+
console.log(` ${i + 1}. ${p.name}${detail ? ` (${detail})` : ""}`);
|
|
138
|
+
});
|
|
139
|
+
const ans = await ask(`\n Pick ${label.toLowerCase()} [1-${list.length}]: `);
|
|
140
|
+
const idx = Number(ans) - 1;
|
|
141
|
+
if (idx >= 0 && idx < list.length) return list[idx];
|
|
142
|
+
return list[0];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function selectPersonas(dump, userHint, bizHint) {
|
|
146
|
+
const config = loadConfig();
|
|
147
|
+
const personas = dump.personas || [];
|
|
148
|
+
const bizPersonas = dump.businessPersonas || [];
|
|
149
|
+
|
|
150
|
+
const effectiveUserHint = userHint || "";
|
|
151
|
+
const effectiveBizHint = bizHint || "";
|
|
152
|
+
const defaultUser = config.defaultUser || "";
|
|
153
|
+
const defaultBiz = config.defaultBusiness || "";
|
|
154
|
+
|
|
155
|
+
const user = await pickFromList(
|
|
156
|
+
"User personas",
|
|
157
|
+
personas,
|
|
158
|
+
effectiveUserHint,
|
|
159
|
+
["name", "profile.fullName"],
|
|
160
|
+
defaultUser,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const biz = await pickFromList(
|
|
164
|
+
"Business personas",
|
|
165
|
+
bizPersonas,
|
|
166
|
+
effectiveBizHint,
|
|
167
|
+
["name", "profile.companyName", "profile.productName"],
|
|
168
|
+
defaultBiz,
|
|
169
|
+
);
|
|
170
|
+
|
|
142
171
|
return { user, biz };
|
|
143
172
|
}
|
|
144
173
|
|