@letterstory/cli 0.44.0 → 0.45.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 +11 -0
- package/lib/cli.mjs +13 -2
- package/lib/commands/admin.mjs +73 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -386,8 +386,19 @@ letterstory admin crm accounts --stage demo_scheduled # with likelihood
|
|
|
386
386
|
letterstory admin crm score <account-id> # recompute; prints the factors
|
|
387
387
|
letterstory admin crm slack-channels [--all] # Watchdog channels + the account each is linked to
|
|
388
388
|
letterstory admin crm link-slack <account-id> C0123ABC # the CRM's watch-only Slack link (`none` unlinks)
|
|
389
|
+
|
|
390
|
+
# One-click onboarding of a won client (key needs crm:write + fleet:onboard + org:create)
|
|
391
|
+
letterstory admin crm onboard <account-id> --domain acme.com \
|
|
392
|
+
--site "Where fintech compliance is heading" --site "" \
|
|
393
|
+
--slack jane@acme.com --invite jane@acme.com:admin --invite bob@acme.com
|
|
394
|
+
letterstory admin crm onboarding <account-id> # step-by-step progress (sites refreshed)
|
|
395
|
+
letterstory admin crm retry-onboarding <onboarding-id> # re-run only the failed steps
|
|
389
396
|
```
|
|
390
397
|
|
|
398
|
+
`onboard` runs org → link → phantom sites → Slack channel → checklist → app invites →
|
|
399
|
+
stage in one call. Each site is a real (not test) fleet build; `--site ""` lets the engine
|
|
400
|
+
infer its beat from the client's homepage.
|
|
401
|
+
|
|
391
402
|
Every trigger — Cal.com bookings, Circleback recordings, Letterhome dinner RSVPs, RB2B
|
|
392
403
|
visits, and what an agent records here — goes through one intake (`ingestSignal`,
|
|
393
404
|
src/lib/crm/signals-intake.ts), so an agent's signal dedupes against a webhook's exactly
|
package/lib/cli.mjs
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
} from "./commands.mjs";
|
|
53
53
|
|
|
54
54
|
// Keep in sync with cli/package.json.
|
|
55
|
-
export const VERSION = "0.
|
|
55
|
+
export const VERSION = "0.45.0";
|
|
56
56
|
|
|
57
57
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
58
58
|
// can't accidentally swallow the id as --json's value.
|
|
@@ -61,6 +61,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
61
61
|
"yes",
|
|
62
62
|
"confirm-missing-target",
|
|
63
63
|
"no-wait",
|
|
64
|
+
"no-checklist",
|
|
64
65
|
"accept",
|
|
65
66
|
"help",
|
|
66
67
|
"version",
|
|
@@ -565,6 +566,9 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
565
566
|
| white-glove [--suggested] | set-white-glove <account-id> on|off [--note "…"]
|
|
566
567
|
| churn [--months 12] | record-churn <account-id> <reason> [--note "…"] [--at ISO]
|
|
567
568
|
[--mrr-cents N] | undo-churn <account-id>
|
|
569
|
+
| onboard <account-id> --domain acme.com [--name "Acme"] [--site "<direction>"]… [--sites N]
|
|
570
|
+
[--slack a@acme.com,b@acme.com] [--invite a@acme.com[:admin]]… [--no-checklist]
|
|
571
|
+
| onboarding <account-id> | retry-onboarding <onboarding-id>
|
|
568
572
|
The staff CRM's pipeline: the inbound signal
|
|
569
573
|
feed, recording a signal with no webhook
|
|
570
574
|
(Bookface, a tweet like), qualifying or
|
|
@@ -574,7 +578,14 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
574
578
|
Churn: 30/90-day logo + revenue rates, who
|
|
575
579
|
churned and why; Stripe cancellations are
|
|
576
580
|
only suggestions until a person records one.
|
|
577
|
-
|
|
581
|
+
Onboard runs org → link → phantom sites →
|
|
582
|
+
Slack channel → checklist → app invites →
|
|
583
|
+
stage in one call (--site "" = infer the
|
|
584
|
+
beat from their homepage); retry re-runs
|
|
585
|
+
only what failed.
|
|
586
|
+
Needs crm:read (reads) / crm:write (writes);
|
|
587
|
+
onboard + retry also need fleet:onboard and
|
|
588
|
+
org:create.
|
|
578
589
|
admin tuning-context [--q "<question>"] The experiment agent's context pack (JSON):
|
|
579
590
|
the pipeline as deployed, levers, measures,
|
|
580
591
|
goals, related history.
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// admin experiments list|show|new|update|note|started|spend|conclude|abandon … (tuning:experiment)
|
|
8
8
|
// admin tuning-context [--q "<question>"] (tuning:experiment)
|
|
9
9
|
// admin tuning-import <brief-results.json> [--experiment <id>] (tuning:experiment)
|
|
10
|
-
// admin crm signals|record|signal|accounts|account|score|slack-channels|link-slack|white-glove|set-white-glove|churn|record-churn|undo-churn … (crm:read / crm:write)
|
|
10
|
+
// admin crm signals|record|signal|accounts|account|score|slack-channels|link-slack|white-glove|set-white-glove|churn|record-churn|undo-churn|onboard|onboarding|retry-onboarding … (crm:read / crm:write)
|
|
11
11
|
// admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
|
|
12
12
|
// [--capabilities a,b] [--onboard] [--about "…"] [--sitemap <url>]
|
|
13
13
|
// [--cms <provider>] [--posts N] [--seers suggest|skip] [--no-visuals]
|
|
@@ -425,6 +425,57 @@ function accountLine(a) {
|
|
|
425
425
|
return `${a.id} [${a.stage}] ${score} ${a.name}${a.domain ? ` (${a.domain})` : ""}${a.isYc ? " · YC" : ""}`;
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
function onboardingLines(o) {
|
|
429
|
+
if (!o) return "(never onboarded from the CRM)";
|
|
430
|
+
const mark = { done: "✓", skipped: "–", failed: "✗", running: "…", pending: "·" };
|
|
431
|
+
return [
|
|
432
|
+
`${o.id} started ${o.startedAt.slice(0, 16).replace("T", " ")}${o.finishedAt ? " finished" : " in progress"}`,
|
|
433
|
+
...o.steps.map(
|
|
434
|
+
(s) =>
|
|
435
|
+
` ${mark[s.status] ?? "?"} ${s.key.padEnd(9)} ${s.status}${s.progress ? ` (${s.progress.done}/${s.progress.total})` : ""}${s.detail ? ` ${s.detail}` : ""}${s.error ? `\n error: ${s.error}` : ""}`
|
|
436
|
+
),
|
|
437
|
+
].join("\n");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* `admin crm onboard` body from flags — the same CrmOnboardBodySchema the desk sends.
|
|
442
|
+
* --site "<direction>" per site (repeatable; "" = infer from the homepage) and/or
|
|
443
|
+
* --sites N (pads with inferred sites); --slack a@x,b@x (bare --slack = the channel, no
|
|
444
|
+
* invites yet); --invite email[:admin|editor] (repeatable); --no-checklist.
|
|
445
|
+
*/
|
|
446
|
+
function onboardBody(flags) {
|
|
447
|
+
const domain = flagStr(flags.domain);
|
|
448
|
+
if (!domain) {
|
|
449
|
+
throw new CliError(
|
|
450
|
+
'Usage: admin crm onboard <account-id> --domain acme.com [--name "Acme"] [--site "<direction>"]… [--sites N] [--slack a@x,b@x] [--invite a@x[:admin]]… [--no-checklist]'
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const sites = flagList(flags.site).map((direction) => ({ direction: direction.trim() || null }));
|
|
454
|
+
const total = flagNum(flags.sites);
|
|
455
|
+
while (total !== undefined && sites.length < total) sites.push({ direction: null });
|
|
456
|
+
const slackEmails = flagList(flags.slack)
|
|
457
|
+
.flatMap((v) => v.split(","))
|
|
458
|
+
.map((e) => e.trim())
|
|
459
|
+
.filter(Boolean);
|
|
460
|
+
const slack = flags.slack === undefined ? null : { emails: slackEmails };
|
|
461
|
+
const invites = flagList(flags.invite)
|
|
462
|
+
.flatMap((v) => v.split(","))
|
|
463
|
+
.map((v) => v.trim())
|
|
464
|
+
.filter(Boolean)
|
|
465
|
+
.map((v) => {
|
|
466
|
+
const [email, role] = v.split(":");
|
|
467
|
+
return { email, role: role === "admin" ? "admin" : "editor" };
|
|
468
|
+
});
|
|
469
|
+
return compact({
|
|
470
|
+
domain,
|
|
471
|
+
name: flagStr(flags.name),
|
|
472
|
+
sites,
|
|
473
|
+
slack,
|
|
474
|
+
invites,
|
|
475
|
+
checklist: flagBool(flags["no-checklist"]) ? false : undefined,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
428
479
|
/**
|
|
429
480
|
* `admin crm …` — the headless half of the staff CRM, so a background agent can watch the
|
|
430
481
|
* pipeline and record the signals that have no webhook (Bookface, tweet likes, DMs).
|
|
@@ -623,9 +674,29 @@ async function cmdAdminCrm(ctx) {
|
|
|
623
674
|
const r = await staffRequest(ctx, "DELETE", `${CRM}/accounts/${id}/churn`);
|
|
624
675
|
return out(r, (x) => `churn undone: ${accountLine(x.account)}`);
|
|
625
676
|
}
|
|
677
|
+
case "onboard": {
|
|
678
|
+
// admin crm onboard <account-id> --domain acme.com … — one-click onboarding of a won
|
|
679
|
+
// client. The key needs crm:write + fleet:onboard + org:create.
|
|
680
|
+
if (!id) throw new CliError("Usage: admin crm onboard <account-id> --domain acme.com … (see --help)");
|
|
681
|
+
const r = await staffPost(ctx, `${CRM}/accounts/${id}/onboard`, onboardBody(flags));
|
|
682
|
+
return out(r, (x) => onboardingLines(x.onboarding));
|
|
683
|
+
}
|
|
684
|
+
case "onboarding": {
|
|
685
|
+
// admin crm onboarding <account-id> — the latest onboarding, sites refreshed.
|
|
686
|
+
if (!id) throw new CliError("Usage: admin crm onboarding <account-id>");
|
|
687
|
+
return out(await staffRequest(ctx, "GET", `${CRM}/accounts/${id}/onboard`), (x) =>
|
|
688
|
+
onboardingLines(x.onboarding)
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
case "retry-onboarding": {
|
|
692
|
+
// admin crm retry-onboarding <onboarding-id> — re-run the failed/pending steps.
|
|
693
|
+
if (!id) throw new CliError("Usage: admin crm retry-onboarding <onboarding-id>");
|
|
694
|
+
const r = await staffPost(ctx, `${CRM}/onboardings/${id}/retry`, {});
|
|
695
|
+
return out(r, (x) => onboardingLines(x.onboarding));
|
|
696
|
+
}
|
|
626
697
|
default:
|
|
627
698
|
throw new CliError(
|
|
628
|
-
"Usage: admin crm signals|record|signal|accounts|account|score|slack-channels|link-slack|white-glove|set-white-glove|churn|record-churn|undo-churn … (see --help)"
|
|
699
|
+
"Usage: admin crm signals|record|signal|accounts|account|score|slack-channels|link-slack|white-glove|set-white-glove|churn|record-churn|undo-churn|onboard|onboarding|retry-onboarding … (see --help)"
|
|
629
700
|
);
|
|
630
701
|
}
|
|
631
702
|
}
|