@floomhq/signaldash 0.39.19 → 0.40.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 +91 -14
- package/bin/sd.mjs +324 -24
- package/lib/x-signal-collector.cjs +257 -0
- package/package.json +3 -2
- package/scripts/collect-x-signals.mjs +133 -0
- package/skills/signaldash/SKILL.md +64 -10
- package/skills/signaldash-safe-usage/SKILL.md +3 -2
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ setup installs both from the same pinned npm package
|
|
|
120
120
|
the human chose to execute:
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
|
-
npx -y @floomhq/signaldash@0.
|
|
123
|
+
npx -y @floomhq/signaldash@0.40.0 <invite-code>
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
Run that command in a terminal, not in an agent chat. Do not ask an agent to
|
|
@@ -145,24 +145,46 @@ SignalDash exposes:
|
|
|
145
145
|
- `sd_inspiration_import(source, source_author?, federico_note?, purpose?, content_kind?, credit_required?, credit_note?, format_description?)`
|
|
146
146
|
- `sd_inspiration_channel_configure(chat_id, provider_id, chat_name, confirm)`
|
|
147
147
|
- `sd_inspiration_channel_set(capture_enabled, confirm)`
|
|
148
|
+
- `sd_x_signal_set(enabled, confirm)`
|
|
149
|
+
- `sd_x_signal_import(feed, claim_key, topic, title, claim_text, source, snapshot, confirm)`
|
|
150
|
+
- `sd_x_signal_add_evidence(id, expected_version, source_kind, source, confirm)`
|
|
151
|
+
- `sd_x_signal_propose(id, expected_version, proposed_text, proposed_claims, confirm)`
|
|
152
|
+
- `sd_x_signal_list(feed, state?, limit?)`
|
|
148
153
|
- `sd_secretary_approve(disposition_id, payload_hash, confirm)`
|
|
149
154
|
- `sd_secretary_reject(disposition_id, confirm)`
|
|
150
155
|
- `li_list_chats`
|
|
151
156
|
- `li_read_messages(chat_id)`
|
|
157
|
+
- `li_get_attachment(chat_id, message_id, attachment_id)`
|
|
152
158
|
- `li_mark_read(chat_id)`
|
|
153
159
|
- `li_start_chat(member_ids, text, confirm?, approval_hash?, dry_run?)`
|
|
154
|
-
- `li_send_message(chat_id?, text?, expected_watermark?, mark_read?, secretary_receipt_id?)`
|
|
160
|
+
- `li_send_message(chat_id?, text?, expected_watermark?, mark_read?, secretary_receipt_id?)` — confirmed sends clear unread by default; `mark_read:false` preserves unread.
|
|
155
161
|
- `li_send_invitation(provider_id, note?, confirm?)`
|
|
156
162
|
- `li_invitations_received(limit?, cursor?)`
|
|
157
163
|
- `li_accept_invitation(invitation_id, confirm)`
|
|
158
164
|
- `li_invitations_sent(limit?, cursor?)`
|
|
159
165
|
- `li_withdraw_invitation(invitation_id, confirm)`
|
|
166
|
+
|
|
167
|
+
The X signal collector is a manual, review-first command. It pins the exact
|
|
168
|
+
Apify actor build, caps each feed at 25 complete dataset rows and $0.05, stores
|
|
169
|
+
the exact raw row plus run/query provenance, and imports no LinkedIn draft:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
APIFY_API_TOKEN=... node scripts/collect-x-signals.mjs \
|
|
173
|
+
--feed fede --feed rocketlist --confirm
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The deployment capability and the authenticated SignalDash user's setting must
|
|
177
|
+
both be enabled. Re-running a completed observation is deterministic; a newer
|
|
178
|
+
observation adds an immutable engagement snapshot. The command creates no
|
|
179
|
+
timer, scheduler entry, Buffer item, or provider write.
|
|
160
180
|
- `sd_contact_state(channel, identifiers, action?, reason?, confirm?)`
|
|
161
181
|
- `sd_budget_status()`
|
|
162
182
|
- `sd_settings_get()`
|
|
163
183
|
- `sd_settings_set(auto_accept_linkedin, auto_accept_linkedin_filters?, confirm)`
|
|
164
184
|
- `sd_auto_accept_status()`
|
|
165
185
|
- `sd_voice_profile(channel, force_recompute?)`
|
|
186
|
+
- `sd_content_automation_report(automation_key, name, schedule, state, confirm, description?, detail?, step?, next_run_at?, artifact_kind?, artifact_id?)`
|
|
187
|
+
- `sd_content_automations()`
|
|
166
188
|
- `li_search_connections(query, filters?, limit?)`
|
|
167
189
|
- `li_discover_people(query, filters?, limit?, confirm?)`
|
|
168
190
|
- `li_create_invitation_batch(source_label, time_zone, targets)`
|
|
@@ -182,8 +204,8 @@ SignalDash exposes:
|
|
|
182
204
|
- `wa_mark_read(chat_id)`
|
|
183
205
|
- `wa_get_attachment(chat_id, message_id, attachment_id)`
|
|
184
206
|
- `wa_transcribe_voice(chat_id, message_id, attachment_id)`
|
|
185
|
-
- `wa_start_chat(member_ids, text, confirm?, approval_hash?, dry_run?)`
|
|
186
|
-
- `wa_send_message(chat_id, text?, attachments?, expected_watermark?, mark_read?)`
|
|
207
|
+
- `wa_start_chat(member_ids, text, subject?, confirm?, approval_hash?, dry_run?)`: `subject` names a new group (2+ members) and is bound into the approval.
|
|
208
|
+
- `wa_send_message(chat_id, text?, attachments?, expected_watermark?, mark_read?)` — confirmed sends clear unread by default; `mark_read:false` preserves unread.
|
|
187
209
|
- `wa_delete_message(chat_id, message_id)`
|
|
188
210
|
- `wa_delete_messages(messages)`
|
|
189
211
|
- `email_list(limit)`
|
|
@@ -196,7 +218,7 @@ SignalDash exposes:
|
|
|
196
218
|
- `li_like_comment(post_id, parent_comment_id, comment_id, expected_watermark?)`
|
|
197
219
|
- `li_delete_message(chat_id, message_id, confirm)`
|
|
198
220
|
- `li_delete_comment(post_id, comment_id, confirm)`
|
|
199
|
-
- `li_draft_post(text, publish, scheduled_at?, content_pipeline_id?, content_pipeline_override?, mentions?, attachments?, first_comment?)`
|
|
221
|
+
- `li_draft_post(text, publish, scheduled_at?, content_pipeline_id?, content_pipeline_override?, mentions?, attachments?, first_comment?, confirm?, approval_hash?)`
|
|
200
222
|
- `li_set_scheduled_post_first_comment(id, first_comment, confirm)`
|
|
201
223
|
- `li_save_post_draft(text, mentions?, attachments?, first_comment?, source_key?, metadata?)`
|
|
202
224
|
- `li_post_drafts()`
|
|
@@ -211,6 +233,10 @@ SignalDash exposes:
|
|
|
211
233
|
- `sd_schedule_message(channel, chat_id, text, scheduled_at, confirm)`
|
|
212
234
|
- `sd_scheduled_messages(state?, channel?)`
|
|
213
235
|
- `sd_cancel_scheduled_message(id, confirm)`
|
|
236
|
+
- `sd_followup_create(channel, thread_id, context, due_at, completion_condition?, auto_close_on_reply?, recipient_label?, origin?, source_message_id?, baseline_at?)`
|
|
237
|
+
- `sd_followups(status?, channel?, overdue?)`
|
|
238
|
+
- `sd_followup_cancel(id, confirm)`
|
|
239
|
+
- `sd_followup_complete(id, confirm)`
|
|
214
240
|
|
|
215
241
|
Every operation runs through the hosted SignalDash backend. Agents never
|
|
216
242
|
receive the Unipile access key.
|
|
@@ -272,13 +298,13 @@ whatever the page's completeness, because presence on an incomplete page is
|
|
|
272
298
|
still presence. `replied_by_me:false` is emitted only when the reply set is
|
|
273
299
|
provably whole, every reply carries a resolvable `author_details.id`, ids are
|
|
274
300
|
unique, every reply belongs to this thread, and no reply is the account
|
|
275
|
-
owner's.
|
|
276
|
-
complete
|
|
277
|
-
empty
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
owner in public. Everything else is `replied_by_me:null` with an explicit
|
|
301
|
+
owner's. Three proofs are accepted: the provider reporting the reply page
|
|
302
|
+
complete; a zero `reply_counter` together with a reply read that came back
|
|
303
|
+
empty; or a single cursorless reply page, shorter than the requested limit,
|
|
304
|
+
holding at least `reply_counter` replies (`reply_counter_met_on_short_page`).
|
|
305
|
+
The reply and like preflights accept the same three. A cursor, a full page, a
|
|
306
|
+
missing counter, or fewer replies read than the counter claims stays unproven,
|
|
307
|
+
because a wrong `false` makes an agent talk over the account owner in public. Everything else is `replied_by_me:null` with an explicit
|
|
282
308
|
reason. The flag is off by default and unresolved comments still carry
|
|
283
309
|
`reply_state.state = "unknown"`, so a miss is never mistaken for a proven
|
|
284
310
|
absence. Resolution is bounded per request by
|
|
@@ -380,11 +406,50 @@ comment have human approval. The stored image bytes and comment text cannot
|
|
|
380
406
|
drift after approval. The scheduler publishes the comment through the same
|
|
381
407
|
connected LinkedIn account immediately after the post. It persists the post ID
|
|
382
408
|
before attempting the comment, so a comment failure never republishes the post.
|
|
409
|
+
On the `li_draft_post` create path, every future post with `attachments` and no
|
|
410
|
+
`content_pipeline_override` must first preview the exact payload without
|
|
411
|
+
`confirm`, then repeat it with `publish:true`, `confirm:true`, and that preview's
|
|
412
|
+
single-use `approval_hash`.
|
|
383
413
|
The scheduler
|
|
384
414
|
uses the same sender binding, action ledger, duplicate guard, daily budget, and
|
|
385
415
|
provider-warning lock as immediate publishing. An interrupted or ambiguous
|
|
386
416
|
execution fails closed and is never retried automatically.
|
|
387
417
|
|
|
418
|
+
A post carries either images or one video, never both. An image is still
|
|
419
|
+
carried as `content_base64` inside the request body. A video is carried as
|
|
420
|
+
`content_url`: one absolute https URL, at most 64 MiB, whose bytes are streamed
|
|
421
|
+
once at publish time and never stored. 31 MB of mp4 is ~42 MB of base64, which
|
|
422
|
+
would have to survive a JSON parse and then live in the `attachments_json` cell
|
|
423
|
+
that is re-read on every list of the queue, so storing the URL is the point
|
|
424
|
+
rather than a convenience. LinkedIn's own feed-video limit is 200 MB; the lower
|
|
425
|
+
ceiling here is because the multipart encoder buffers the body and then
|
|
426
|
+
concatenates it, so a publish holds roughly twice the video in memory.
|
|
427
|
+
|
|
428
|
+
That fetch is fenced, because a server that follows a URL its caller supplied
|
|
429
|
+
is otherwise an SSRF hole: https only; every resolved address checked against
|
|
430
|
+
the private, loopback, link-local, unique-local and reserved ranges in IPv4 and
|
|
431
|
+
IPv6, with the connection pinned to the address that passed so a second
|
|
432
|
+
resolution cannot substitute a private one; every redirect re-running the whole
|
|
433
|
+
check under a hop cap; the ceiling enforced mid-stream rather than from a
|
|
434
|
+
Content-Length the same server supplied; one deadline across the whole
|
|
435
|
+
operation; and an arriving Content-Type that must equal the declared one. The
|
|
436
|
+
fetch runs before the provider write, so a URL that has gone dead costs no
|
|
437
|
+
action budget, no idempotency record and no sender lock: the row lands on
|
|
438
|
+
`failed` carrying the exact refusal code.
|
|
439
|
+
|
|
440
|
+
Two things the fence alone does not give. The arriving `Content-Type` is a
|
|
441
|
+
claim by the server being distrusted, so the bytes must additionally open with
|
|
442
|
+
an ISO `ftyp` box or the publish fails `media_not_a_video`. And because the
|
|
443
|
+
bytes are fetched later, approving a video approves a URL: whoever controls
|
|
444
|
+
that host decides what reaches LinkedIn between the approval and the scheduled
|
|
445
|
+
minute. An attachment may therefore carry `content_sha256`, the exact lowercase
|
|
446
|
+
hex SHA-256 of the file; the fetched bytes are hashed and compared before
|
|
447
|
+
anything is handed to the multipart encoder, and a mismatch fails the row
|
|
448
|
+
`media_digest_mismatch`. It is bound into the approval hash alongside the URL,
|
|
449
|
+
so an approval never covers a different video. Without it the guarantee is only
|
|
450
|
+
that the URL did not change after approval, which is why the review page shows
|
|
451
|
+
the URL itself rather than a label.
|
|
452
|
+
|
|
388
453
|
Arbitrary saved LinkedIn drafts use a separate durable store and never enter
|
|
389
454
|
the scheduling or publishing worker. `li_save_post_draft` stores exact text,
|
|
390
455
|
up to 25 mentions, up to 20 ordered images, and an optional first comment.
|
|
@@ -494,8 +559,9 @@ their normal expiry.
|
|
|
494
559
|
|
|
495
560
|
`li_scheduled_post_attachment` is the agent-side path to the same stored bytes
|
|
496
561
|
the preview page renders, without a browser session: it returns one attachment
|
|
497
|
-
of one active post
|
|
498
|
-
`
|
|
562
|
+
of one active post with its `filename` and its stored `content_type` verbatim,
|
|
563
|
+
as `content_base64` for an image or as `content_url` for a video whose bytes
|
|
564
|
+
are never stored here. It never fetches a URL on a caller behalf. It is scoped by the same `data_ref` and the same
|
|
499
565
|
draft/scheduled/executing filter as `li_scheduled_post_preview`, so a cancelled
|
|
500
566
|
or published post, a foreign post, and a missing index are one indistinguishable
|
|
501
567
|
404. The bytes are deliberately not part of the preview response, which reports
|
|
@@ -741,6 +807,17 @@ untracked files, and submodule state present when the process started.
|
|
|
741
807
|
service restart. It defaults to enabled and exposes no credential, account, or
|
|
742
808
|
message data.
|
|
743
809
|
|
|
810
|
+
The sanctioned WhatsApp sender is tracked at `ops/signaldash-wa-send`. Install
|
|
811
|
+
that exact reviewed helper with:
|
|
812
|
+
|
|
813
|
+
```bash
|
|
814
|
+
sudo install -m 0755 ops/signaldash-wa-send /usr/local/bin/signaldash-wa-send
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
The helper rejects Unicode U+2014 locally. The server repeats the same check at
|
|
818
|
+
the authoritative write boundary so older clients and alternate callers cannot
|
|
819
|
+
bypass it.
|
|
820
|
+
|
|
744
821
|
## Releasing
|
|
745
822
|
|
|
746
823
|
A release is a tag push. `.github/workflows/release.yml` runs `./release.sh` on
|