@amdahl/cli 0.4.1 → 0.4.9

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.
Files changed (3) hide show
  1. package/README.md +240 -5
  2. package/dist/amdahl.js +2758 -1225
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -319,7 +319,13 @@ amdahl whoami # the account, workspace and role you are signed in
319
319
  amdahl status # can this workspace optimize, and what blocks it
320
320
  amdahl optimize draft.md # rewrite one message (or pipe it in with -, or run it bare and paste)
321
321
  amdahl optimize --list drafts.csv # rewrite a whole list in one batch
322
+ amdahl optimize --list drafts.csv --for lemlist --channel email # and a CSV lemlist imports
323
+ amdahl grade draft.md # grade a message: a score and the lines behind it
324
+ amdahl prompt prompt.md # grade or improve a reusable prompt by the emails it writes
322
325
  amdahl search "what objections came up in lost deals?" # ask about your customers
326
+ amdahl recipes # the console's questions; amdahl search --recipe <id> runs one
327
+ amdahl data # what the workspace holds, and what a search can filter on
328
+ amdahl runs # your latest runs; amdahl run <run-id> opens one
323
329
  amdahl connections # what is connected, and whether it is healthy
324
330
  amdahl connect hubspot # open the console's add page, to add a CRM or call recorder
325
331
  amdahl open # open this workspace in the console
@@ -363,8 +369,18 @@ amdahl auth token
363
369
  amdahl status
364
370
  amdahl optimize [<file>... | -] [--channel email|linkedin] [--evidence off|workspace]
365
371
  [--on-unsupported flag|remove] [--tries] [--context <file.json>]
366
- amdahl optimize --list <file.csv|file.jsonl> [--no-wait] [--batch <id>] [--force] [the optimize flags]
372
+ amdahl optimize --list <file.csv|file.jsonl> [--no-wait] [--batch <id>] [--force] [--for <tool>] [the optimize flags]
373
+ amdahl grade <file|-> [--channel email|linkedin] [--evidence off|workspace] [--context <file.json>]
374
+ amdahl prompt <file|-> [--mode improve|grade] [--recipients <file.json>] [--channel email|linkedin]
375
+ [--context <file.json>] [--no-wait]
376
+ amdahl prompt --run <id> [--no-wait]
377
+ amdahl runs [--limit <n>]
378
+ amdahl run <run-id>
379
+ amdahl data [--surface interactions|deals|deal_qualification]
380
+ amdahl recipes
367
381
  amdahl search "<question>" [--research] [--limit <n>]
382
+ amdahl search --recipe <id> [--account|--products|--messages|--objection <text>]
383
+ amdahl search --research-id <id>
368
384
  amdahl connections [--all]
369
385
  amdahl connect [<connector>] [--no-browser]
370
386
  amdahl open [--no-browser]
@@ -421,6 +437,35 @@ cited answer, then what the answer rests on under `Details:` (its
421
437
  `content_blocks`, in the order the job presented them), then up to three
422
438
  questions to ask next.
423
439
 
440
+ When a research job starts, the CLI prints its id (`Research id: ...`) and how
441
+ to read it later. `amdahl search --research-id <id>` reads a job started
442
+ earlier and waits for it, then prints it as `--research` does (with
443
+ `--json`, `{ok, research}`). It needs a key that can read research (a
444
+ Customer agent key); a sign-in gets exit 4 and the line on the key. A job
445
+ that is still running at the end of the wait exits 1 and gives the id again.
446
+
447
+ ### Recipes
448
+
449
+ ```sh
450
+ amdahl recipes
451
+ amdahl search --recipe account-brief --account "Acme"
452
+ amdahl search --recipe whitespace --products "Analytics, Forecasting"
453
+ amdahl search --recipe stalled-deals
454
+ ```
455
+
456
+ `recipes` lists the console's recipe library: the same ids, titles,
457
+ fill-ins and search questions (`src/recipes-data.ts` names the file and commit
458
+ they are copied from). Twelve of the library's recipes run as a search; the
459
+ rest are prompts for an AI client and are in the console. `search --recipe
460
+ <id>` asks a recipe's question with its fill-in in place. A fill-in is a flag
461
+ named for it (`--account`, `--products`, `--messages`, `--objection`); one the
462
+ recipe needs and was not given, or one it has no use for, exits 2 before any
463
+ request. A fill-in is cut at 300 characters, as the console cuts it, and the
464
+ CLI says so. `--research` and `--limit` work with a recipe; a question beside
465
+ `--recipe` is refused. With `--json` the result is the same answer a search
466
+ gives, and `amdahl recipes --json` lists every recipe with its question and
467
+ fill-ins.
468
+
424
469
  Each table prints as a plain ASCII table that fits 80 columns: a long cell
425
470
  wraps (a column is narrowed to its longest word first, so an id or a timestamp
426
471
  stays whole), a cell past four lines is cut with `...`, a word wider than its
@@ -444,6 +489,115 @@ prints its message, which carries the console link, then
444
489
  `Connect your CRM or call recorder: amdahl connect`. With no rows, it prints
445
490
  the server's one line on how fresh the data is, or `Nothing matched.`
446
491
 
492
+ ## Grade a message
493
+
494
+ ```sh
495
+ amdahl grade draft.md --evidence workspace
496
+ ```
497
+
498
+ `grade` grades a draft and writes nothing (`POST /messages/optimize` with
499
+ `mode: "grade"`). The file is the message; `-` reads stdin; with no file, at a
500
+ terminal, you paste it. It takes `--channel`, `--evidence` and `--context`;
501
+ `--tries` and `--on-unsupported` belong to a rewrite, which a grade is not,
502
+ so they are unknown options here.
503
+
504
+ ```
505
+ Grade 4.2 of 5: 4 of 5 lines passed.
506
+ It meets the bar of 4.2.
507
+
508
+ The lines it rests on:
509
+ pass Relevant positioning
510
+ It names what Corvid shipped.
511
+ ...
512
+ fail Differentiation
513
+ It reads like boilerplate.
514
+
515
+ Checked against your calls.
516
+
517
+ This is one reading by claude-sonnet-5-5. It measures the message against the
518
+ rubric and your evidence. It does not predict replies. ...
519
+ ```
520
+
521
+ The score is printed as the API returns it, and it always comes with the five
522
+ lines it rests on, each with its verdict and reason. Without
523
+ `--evidence workspace` only the ask is evaluated: the four lines that read your
524
+ customers' calls say "Not evaluated", and the score says it rests on 1 of the 5
525
+ lines. A line that could not be evaluated is not a fail. When no line could be,
526
+ the grade is `Not graded.`, never 0. Every grade says it is one reading and does
527
+ not predict replies. The run id comes last; `amdahl run <id>` opens it. With
528
+ `--json` it prints `{ok, file, result}`: the grade is `.result.grade`.
529
+
530
+ ## Grade or improve a prompt
531
+
532
+ ```sh
533
+ amdahl prompt prompt.md --recipients recipients.json
534
+ amdahl prompt prompt.md --mode grade
535
+ amdahl prompt --run <id>
536
+ ```
537
+
538
+ `prompt` grades or improves a reusable prompt (the instructions a writer
539
+ follows to write an outbound email to any recipient) by the emails it writes
540
+ (`POST /messages/optimize_prompt`, then `GET /messages/optimize_prompt/:id`).
541
+ One message is for `optimize`: a prompt that names one recipient is refused.
542
+ `--mode improve` (the default) revises the prompt and returns it only when the
543
+ revision wrote better email on recipients held out of the revising; `--mode
544
+ grade` writes one email per recipient and grades each. `--recipients` is a
545
+ JSON file of 1 to 6 `{"recipient": {...}}` objects (`name`, `company`, `email`,
546
+ `domain`, `linkedin_url`, `attributes`; also `recipient_notes` and `label`).
547
+ Improve needs two or more. Left out, Amdahl picks up to six of your workspace's
548
+ accounts. `--channel` and `--context` are as in `optimize`.
549
+
550
+ A run takes a few minutes. The command prints the run id at once and waits
551
+ (one read of up to 30 seconds after another, up to 20 minutes); `--no-wait`
552
+ starts it and stops, and `--run <id>` reads it later. The revised prompt, when
553
+ there is one, goes to stdout and the report to stderr, so
554
+ `amdahl prompt p.md > better.md` saves the prompt. The report gives the
555
+ prompt's grade, the emails it wrote that the grade rests on, and a lift only
556
+ when it is reportable (otherwise why not: for example fewer than 3 held-out
557
+ emails on a side). Without a reportable lift it prints the original's mean
558
+ alone and says a revision was kept: the revised and the original means are set
559
+ against each other only when the lift is reportable. The emails that trained the revision are not lift and are
560
+ in `--json`. A grade is one reading and does not predict replies; a mean of
561
+ null is "not graded". Exit 1 when the run failed or is still going. With `--json`
562
+ it prints `{ok, file, started, result}`.
563
+
564
+ ## Your runs
565
+
566
+ ```sh
567
+ amdahl runs
568
+ amdahl run 9fd4f2e3-bc8e-436b-86d7-a677a89e8d16
569
+ ```
570
+
571
+ `run <run-id>` shows one optimize run you made (`GET
572
+ /messages/optimize/runs/:id`): your draft, then the message Amdahl returned,
573
+ what it left out and how much context you sent. Only the person who made a run
574
+ can open it. A grade writes no message, and says so. A prompt run's id starts
575
+ with `opt_`: read it with `amdahl prompt --run <id>`.
576
+
577
+ `runs` lists the latest runs made **from this computer**: the API has no read
578
+ that lists a person's runs (the console keeps its own list in the browser), so
579
+ the CLI keeps the ids it started in `runs.json` beside `config.json`: the run
580
+ id, when, the kind (`optimize`, `grade` or `prompt`), the channel and the
581
+ workspace, for the last 50 runs, in an owner-only file. Never a
582
+ message, a draft or a prompt. Runs made in the console or an AI client are not
583
+ in it, but their ids open with `amdahl run`. `--limit <n>` shows more than the
584
+ latest ten; `--json` prints `{ok, source, note, total, runs}`.
585
+
586
+ ## What the workspace holds
587
+
588
+ ```sh
589
+ amdahl data
590
+ amdahl data --surface deals
591
+ ```
592
+
593
+ `data` shows what a search can read: each surface's live row count and the
594
+ span of its dates (`GET /search/overview`), then the fields a search can
595
+ filter on (`GET /search/fields`). A count that could not be read says
596
+ `unknown`, never 0. `--surface interactions|deals|deal_qualification` lists
597
+ one surface's fields with their type, meaning and the values they are known to
598
+ hold. Every line fits 80 columns. With `--json` it prints `{ok, overview,
599
+ fields}`.
600
+
447
601
  ## Connections
448
602
 
449
603
  ```sh
@@ -507,7 +661,8 @@ The list is a CSV file with a header row, or a JSONL file (`.jsonl` or
507
661
  `.ndjson`) with one batch item, a JSON object, per line. A CSV has a
508
662
  `message` column and may have `item_id`, `channel`, `evidence`,
509
663
  `on_unsupported`, `facts` and `rules`; facts and rules take one per line
510
- inside the cell. Any other column (a CRM export has many) is not sent: one
664
+ inside the cell, and a channel is read in any case (`LinkedIn` is `linkedin`).
665
+ Any other column (a CRM export has many) is not sent: one
511
666
  line names it, and it is copied to the results file in its place. Only a
512
667
  missing `message` column, or a column Amdahl reads that appears twice (a
513
668
  doubled `message`, say), is refused. A JSONL item takes any field a single
@@ -544,6 +699,65 @@ results go next to that list and the list must have as many messages as the
544
699
  batch has items (exit 2 otherwise), otherwise they go to `<id>.optimized.jsonl`
545
700
  here. `--no-wait` with `--batch` exits 2: it only queues a new list.
546
701
 
702
+ ### Send the list to your sequencer
703
+
704
+ ```sh
705
+ amdahl optimize --list drafts.csv --for lemlist --channel email
706
+ ```
707
+
708
+ `--for <tool>` (a CSV list only) also writes the messages Amdahl wrote as a
709
+ file that tool imports, next to the results, and prints three steps: where to
710
+ import it, what to map, and what to paste into the step. The file is
711
+ `<list>-<tool>.csv`: `drafts.csv` for lemlist gets `drafts-lemlist.csv`. The
712
+ console's Send to writes the same file for the same list.
713
+
714
+ ```
715
+ Results: drafts.optimized.csv
716
+ Import file for lemlist: drafts-lemlist.csv
717
+ 38 messages ready for lemlist. 2 left out: 1 failed, 1 with no email.
718
+ 1. Import: In your campaign, open Leads list > Import new leads > CSV.
719
+ 2. email and the name columns map on their own. amdahl_subject and
720
+ amdahl_message become custom variables.
721
+ 3. Paste into your step: Subject {{amdahl_subject}}, Body {{amdahl_message}}
722
+ ```
723
+
724
+ - **Tools:** `instantly`, `smartlead`, `lemlist`, `heyreach`, `reply_io`,
725
+ `apollo`, `outreach`, `salesloft`, `hubspot`, `lgm` (La Growth Machine),
726
+ `woodpecker`, and `other` for any tool with a CSV import. Gmail and Outlook
727
+ send from a mailbox, so they have no import file.
728
+ - **Channel:** a file is for one channel. A tool that takes one channel needs no
729
+ flag: HeyReach and La Growth Machine are LinkedIn, and the rest are email,
730
+ except lemlist, Reply.io and `other`, which take both. For those, add
731
+ `--channel email` or `--channel linkedin`, or put the channel in the list's
732
+ `channel` column. A list with both channels is refused.
733
+ - **Rows:** only leads Amdahl wrote. A lead that failed, needed input or was not
734
+ written is left out, and so is one with no email (no LinkedIn URL for
735
+ LinkedIn; HeyReach also needs a first and a last name). The line after the
736
+ count says how many and why.
737
+ - **Columns:** your list's `email`, `first_name`, `last_name`, `company` and
738
+ `linkedin_url`, found by header in any common spelling and named as the tool
739
+ names them (lemlist `firstName`, `companyName`; Instantly and Smartlead
740
+ `company_name`), then `amdahl_subject` and `amdahl_message` (just
741
+ `amdahl_message` for LinkedIn), then every other column of your list in its
742
+ order, as lowercase snake_case. The column the message came from (the first
743
+ whose header names a draft, such as `message`, `draft` or `body`) and the columns Amdahl
744
+ reads itself (`message`, `channel`, `evidence`, `on_unsupported`, `facts`,
745
+ `rules` and `item_id`, matched on the snake_case header, so `Item ID` too)
746
+ are not in the file. Your tool imports a value as it is, so no cell gets the `'`
747
+ guard that the results file has: a `-20% off` subject or a `+1 415...` phone
748
+ number arrives as written. Line breaks are CRLF, and there is no byte-order
749
+ mark.
750
+ - **Checked first:** before anything is queued, a list the tool cannot import
751
+ exits 2: no email column for an email file, no LinkedIn URL column for a
752
+ LinkedIn one, a JSONL list (the file keeps your columns), a file that already
753
+ exists (`--force` replaces it). `--no-wait` queues the batch, and the line it
754
+ prints to pick it up carries `--for` and `--channel`.
755
+ - **Under `--json`:** the file is still written, no steps are printed, and the
756
+ batch gains `send_to`: `tool`, `channel`, `file` (null when no lead was
757
+ ready), `ready`, `left_out` (`failed`, `not_written`, `needs_input`,
758
+ `no_contact`) and `over_limit`. With no lead ready, no file is written and
759
+ the exit is 1.
760
+
547
761
  ## After you send
548
762
 
549
763
  ```sh
@@ -564,8 +778,9 @@ stores two.
564
778
  (`POST /messages/report_send`). A report from a CLI sign-in is stored as
565
779
  `reported_via: cli`. The flags are the report's fields: `--provider`
566
780
  (`lemlist`, `instantly`, `outreach`, `salesloft`, `hubspot`, `salesforce`,
567
- `gmail`, `heyreach`, `apollo`, `linkedin_manual`, `other`; the server's
568
- list, so use `other` for a sender that is not on it), `--channel` (`email`,
781
+ `gmail`, `heyreach`, `apollo`, `smartlead`, `reply_io`, `lgm`, `woodpecker`,
782
+ `linkedin_manual`, `other`; the server's list, so use `other` for a sender that
783
+ is not on it), `--channel` (`email`,
569
784
  `linkedin_invite`, `linkedin_message`, `linkedin_inmail`; `linkedin` is
570
785
  accepted and means `linkedin_message`, as in `amdahl optimize --channel`),
571
786
  `--sent-as` (`returned` or `edited`) and `--sent-at` (a time with a time
@@ -592,6 +807,26 @@ server's limits first, and a mistake is a usage error that names the flag. It
592
807
  counts a text's length as the server does, in UTF-16 units, so an emoji counts
593
808
  as two. A send the server did not store prints why and exits 1.
594
809
 
810
+ ### Asking what is missing
811
+
812
+ At a terminal, `feedback` and `sent` ask for what you leave out instead of
813
+ exiting 2, with a default for each, and never more than three questions.
814
+ `feedback <run-id>` asks how you know (`--evidence`), then what happened
815
+ (`--outcome`) when you gave no outcome, text or comment. `sent <run-id>` asks
816
+ where it went out (the providers, a menu), who it went to (a recipient, when
817
+ you gave no send id or recipient) and whether you changed it first (paste the
818
+ message as sent, or press Enter for as returned; with `--sent-text` given, a
819
+ yes or no). It never asks for the channel. When it asks for something else
820
+ and `--channel` is not given, it picks one and prints it before it sends: email
821
+ for an email address as the recipient, LinkedIn for a LinkedIn profile, else the
822
+ run's channel (when this computer made the run), else the provider's. If
823
+ `--channel` is the only thing missing, it is a usage error: give it. What you gave is checked first,
824
+ so a typo costs no answers, and Ctrl+C sends nothing. With `--json`, in an
825
+ agent (`CLAUDECODE`, `CURSOR_AGENT`), in CI, with `AMDAHL_NO_PROMPT=1` or with
826
+ stdin or stdout not a terminal (`amdahl sent <id> | tee`), nothing is asked and a
827
+ missing flag is the usage error it always was. `grade` and `prompt` with no
828
+ file paste only under the same rule.
829
+
595
830
  ## Next steps
596
831
 
597
832
  Human output names the next step where there is one, in at most two extra
@@ -858,7 +1093,7 @@ code shown in the terminal; then the key is printed once and stored nowhere.
858
1093
  | code | meaning |
859
1094
  |---|---|
860
1095
  | 0 | ok |
861
- | 1 | general (`login_timeout`, `state_mismatch`, optimize `ok: false`, a failed item in `optimize --list`, a send `sent` could not store, research that failed or is still running, a `skills add` refusal, `invalid_config`, bare `amdahl` with no terminal that found no client) |
1096
+ | 1 | general (`login_timeout`, `state_mismatch`, optimize `ok: false`, a failed item in `optimize --list`, a send `sent` could not store, research that failed or is still running, a prompt run that failed or is still going, a `skills add` refusal, `invalid_config`, bare `amdahl` with no terminal that found no client) |
862
1097
  | 2 | usage (also bare `amdahl` in CI, with `AMDAHL_NO_PROMPT=1` or with `--json`) |
863
1098
  | 3 | not signed in (401, `invalid_grant`, no credential) |
864
1099
  | 4 | forbidden (403, `oauth_required`, `cli_client_required`, `not_admin`, `missing_scope`; also research that started but this credential cannot read) |