@localpulse/cli 0.0.6 → 0.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localpulse/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Local Pulse CLI — ingest event posters, search events, manage credentials",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.test.ts CHANGED
@@ -156,7 +156,8 @@ describe("localpulse", () => {
156
156
  expect(stdout).toContain("Title: Test Night");
157
157
  expect(stdout).toContain("Date: 2026-03-14T22:00:00+01:00");
158
158
  expect(stdout).toContain("Venue: Shelter (Amsterdam) [ChIJ123]");
159
- expect(stdout).toContain("Featured: 1 (DJ Nobu)");
159
+ expect(stdout).toContain("Featured: 1");
160
+ expect(stdout).toContain("DJ Nobu — 1 socials, ");
160
161
  expect(stdout).toContain("Organizer: Dekmantel");
161
162
  expect(stdout).toContain("URLs: 2 source(s)");
162
163
  expect(stdout).toContain("Ticket: https://tickets.example.com");
@@ -192,7 +193,10 @@ describe("localpulse", () => {
192
193
  expect(result.parsed.city).toBe("Amsterdam");
193
194
  expect(result.parsed.venue_place_id).toBe("ChIJ123");
194
195
  expect(result.parsed.featured_count).toBe(2);
195
- expect(result.parsed.featured_names).toEqual(["DJ Nobu", "Nene H"]);
196
+ expect(result.parsed.featured[0].name).toBe("DJ Nobu");
197
+ expect(result.parsed.featured[0].socials_count).toBe(1);
198
+ expect(result.parsed.featured[1].name).toBe("Nene H");
199
+ expect(result.parsed.featured[1].socials_count).toBe(1);
196
200
  expect(result.parsed.organizer).toBe("Dekmantel");
197
201
  expect(result.parsed.urls_count).toBe(2);
198
202
  expect(result.parsed.ticket_url).toBe("https://tickets.example.com");
@@ -213,7 +217,7 @@ describe("localpulse", () => {
213
217
  expect(stdout).toContain("Event name (required)");
214
218
  expect(stdout).toContain("ISO 8601 datetime (required)");
215
219
  expect(stdout).toContain("Kind of event (required)");
216
- expect(stdout).toContain("Source pages (required, 2+)");
220
+ expect(stdout).toContain("Scrapeable source pages (required, 1+)");
217
221
  expect(stdout).toContain('required unless price is "Free"');
218
222
  });
219
223
 
package/src/index.ts CHANGED
@@ -356,8 +356,12 @@ function formatDryRunSummary(
356
356
  }
357
357
 
358
358
  if (payload.featured?.length) {
359
- const names = payload.featured.map((p) => p.name).join(", ");
360
- lines.push(` Featured: ${payload.featured.length} (${names})`);
359
+ lines.push(` Featured: ${payload.featured.length}`);
360
+ for (const p of payload.featured) {
361
+ const socialsCount = p.socials?.length ?? 0;
362
+ const contextWords = p.context?.trim().split(/\s+/).length ?? 0;
363
+ lines.push(` ${p.name} — ${socialsCount} socials, ${contextWords}w context`);
364
+ }
361
365
  }
362
366
 
363
367
  if (payload.organizer?.name) {
@@ -396,7 +400,11 @@ function buildDryRunJsonResult(
396
400
  city: opts.city ?? null,
397
401
  venue_place_id: opts.venuePlaceId ?? null,
398
402
  featured_count: payload.featured?.length ?? 0,
399
- featured_names: payload.featured?.map((p) => p.name) ?? [],
403
+ featured: payload.featured?.map((p) => ({
404
+ name: p.name,
405
+ socials_count: p.socials?.length ?? 0,
406
+ context_words: p.context?.trim().split(/\s+/).length ?? 0,
407
+ })) ?? [],
400
408
  organizer: payload.organizer?.name ?? null,
401
409
  urls_count: opts.urls?.length ?? 0,
402
410
  ticket_url: opts.ticketUrl ?? null,
@@ -788,8 +796,9 @@ Research payload:
788
796
  2. Browse their recent posts or Reels for the event announcement.
789
797
  3. Check who is tagged in that post — these are verified performer handles.
790
798
  4. Also check the caption for @mentions, #hashtags, and links.
791
- 5. Visit each tagged profile: their bio contains verified links (website,
792
- Spotify, Bandcamp, YouTube) and genre/context info for featured[].context.
799
+ 5. OPEN each tagged profile in the browser. Extract every link from their bio
800
+ (website, Spotify, Bandcamp, YouTube, SoundCloud). This is how you populate
801
+ featured[].socials[] and featured[].context. Do NOT just find the URL — visit it.
793
802
  6. Check the venue's Tagged tab for posts where others tag the venue —
794
803
  artists promoting the event often tag the venue and each other.
795
804
 
@@ -802,7 +811,9 @@ Research payload:
802
811
  should appear in featured[] with a type that fits (chef, DJ, host…).
803
812
 
804
813
  Quality checklist (aim to fill as many as possible):
805
- ✓ Featured person socials: Instagram (verified via tags), website, Spotify, Bandcamp, RA
814
+ ✓ Featured person socials: Instagram (verified via tags), website, and:
815
+ - Musicians/DJs: Spotify, Bandcamp, SoundCloud, RA
816
+ - Other types (chefs, hosts, speakers): personal website is usually enough
806
817
  ✓ Featured person context: pull from Instagram bio — genre, notable work, links
807
818
  ✓ Organizer socials: Instagram, website, RA promoter page
808
819
  ✓ Venue google_place_id (search Google Maps → share → extract place ID)
@@ -841,8 +852,11 @@ Research payload:
841
852
  "listening session", "pop-up dinner", "food event",
842
853
  "tasting", "market"
843
854
  .price Ticket price info: "€15-25", "Free", "Sold out"
844
- .urls[] Source pages (required, 2+): RA, venue site, Facebook event
845
- .ticket_url Direct ticketing URL (required unless price is "Free")
855
+ .urls[] Scrapeable source pages (required, 1+): venue page, artist
856
+ site, event listing. These get scraped for enrichment
857
+ use publicly accessible, content-rich pages. NOT ticket URLs.
858
+ .ticket_url Ticketing/purchase URL only (required unless price is "Free");
859
+ do NOT duplicate in event.urls
846
860
  .context Schedule, door policy, age restrictions, special notes
847
861
 
848
862
  context Anything that doesn't fit above: background on the
@@ -81,8 +81,8 @@ describe("auditResearchPayload", () => {
81
81
  const result = auditResearchPayload({
82
82
  featured: [{ name: "Sherin Kalam" }],
83
83
  });
84
- const socials = result.findings.find((f) => f.field === "featured[0].socials");
85
- expect(socials!.action).toContain("Sherin Kalam");
84
+ const context = result.findings.find((f) => f.field === "featured[0].context");
85
+ expect(context!.action).toContain("Sherin Kalam");
86
86
  });
87
87
 
88
88
  // --- organizer ---
@@ -159,16 +159,7 @@ describe("auditResearchPayload", () => {
159
159
  expect(result.findings.find((f) => f.field === "event.urls")).toBeDefined();
160
160
  });
161
161
 
162
- it("flags event with only 1 url", () => {
163
- const result = auditResearchPayload({
164
- event: { urls: ["https://example.com"] },
165
- });
166
- const f = result.findings.find((f) => f.field === "event.urls");
167
- expect(f).toBeDefined();
168
- expect(f!.message).toContain("Only 1");
169
- });
170
-
171
- it("does not flag event with 2+ urls", () => {
162
+ it("does not flag event with 1+ urls", () => {
172
163
  const result = auditResearchPayload({
173
164
  event: { urls: ["https://example.com", "https://ra.co/events/123"] },
174
165
  });
@@ -57,7 +57,7 @@ function auditFeatured(payload: ResearchPayload): AuditFinding[] {
57
57
  finding(
58
58
  `featured[${i}].socials`,
59
59
  `${label} has no social profiles.`,
60
- `Check the venue/organizer's Instagram for posts about this eventlook for @${person.name.toLowerCase().replace(/\s+/g, '')} in tags or caption. If not tagged, search '${person.name} instagram'. Add verified URLs to featured[${i}].socials[].`,
60
+ `Find their Instagram (check venue/organizer posts for @tags). Then OPEN the profile extract every link from their bio (Spotify, Bandcamp, YouTube, website). Add all verified URLs to featured[${i}].socials[].`,
61
61
  ),
62
62
  );
63
63
  }
@@ -67,7 +67,7 @@ function auditFeatured(payload: ResearchPayload): AuditFinding[] {
67
67
  finding(
68
68
  `featured[${i}].context`,
69
69
  `${label} has no context.`,
70
- `Visit ${person.name}'s Instagram profile pull genre, bio, and notable work for featured[${i}].context. Include any Spotify/Bandcamp/website links from their bio.`,
70
+ `Open ${person.name}'s Instagram profile and website. Pull their bio, genre, notable releases/work, and collaborations for featured[${i}].context. For musicians: include Spotify/Bandcamp links from their bio.`,
71
71
  ),
72
72
  );
73
73
  }
@@ -88,7 +88,7 @@ function auditOrganizer(payload: ResearchPayload): AuditFinding[] {
88
88
  finding(
89
89
  "organizer.socials",
90
90
  `Organizer '${name}' has no social profiles.`,
91
- `Find '${name}' on Instagram (browse their profile directly). Pull socials from their bio. Add URLs to organizer.socials[].`,
91
+ `Find '${name}' on Instagram. OPEN the profile extract website, RA page, and other links from their bio. Add all to organizer.socials[].`,
92
92
  ),
93
93
  ];
94
94
  }
@@ -159,16 +159,8 @@ function auditEvent(payload: ResearchPayload): AuditFinding[] {
159
159
  findings.push(
160
160
  finding(
161
161
  "event.urls",
162
- "No event URLs provided.",
163
- "Add the source page URL to event.urls[]. Search for the event on RA, Facebook, or the venue's website and add those too.",
164
- ),
165
- );
166
- } else if (payload.event.urls.length === 1) {
167
- findings.push(
168
- finding(
169
- "event.urls",
170
- "Only 1 event URL provided.",
171
- "Search for this event on RA (ra.co), Facebook Events, or the venue's website and add additional URLs to event.urls[].",
162
+ "No source URLs provided.",
163
+ "Add the event/venue page URL to event.urls[]. These pages get scraped for enrichment use publicly accessible pages with useful content (venue page, artist site, event listing). Do NOT put ticketing URLs here.",
172
164
  ),
173
165
  );
174
166
  }