@amdahl/cli 0.4.0 → 0.4.2

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 CHANGED
@@ -44,9 +44,8 @@ Full steps: https://amdahl.ai/connect
44
44
  ## Install
45
45
 
46
46
  Needs Node.js 22 or later. Node.js 20 reached end of life in April 2026 and
47
- gets no security fixes. `amdahl` still starts on it, but npm warns when it
48
- installs. On a Node older than 20, `amdahl` stops before it does anything,
49
- names the version it found, and links the Node.js download page.
47
+ gets no security fixes. On a Node older than 22, `amdahl` stops before it does
48
+ anything, names the version it found, and links the Node.js download page.
50
49
 
51
50
  ```sh
52
51
  # Run once, with no install
@@ -320,6 +319,7 @@ amdahl whoami # the account, workspace and role you are signed in
320
319
  amdahl status # can this workspace optimize, and what blocks it
321
320
  amdahl optimize draft.md # rewrite one message (or pipe it in with -, or run it bare and paste)
322
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
323
  amdahl search "what objections came up in lost deals?" # ask about your customers
324
324
  amdahl connections # what is connected, and whether it is healthy
325
325
  amdahl connect hubspot # open the console's add page, to add a CRM or call recorder
@@ -364,7 +364,7 @@ amdahl auth token
364
364
  amdahl status
365
365
  amdahl optimize [<file>... | -] [--channel email|linkedin] [--evidence off|workspace]
366
366
  [--on-unsupported flag|remove] [--tries] [--context <file.json>]
367
- amdahl optimize --list <file.csv|file.jsonl> [--no-wait] [--batch <id>] [--force] [the optimize flags]
367
+ amdahl optimize --list <file.csv|file.jsonl> [--no-wait] [--batch <id>] [--force] [--for <tool>] [the optimize flags]
368
368
  amdahl search "<question>" [--research] [--limit <n>]
369
369
  amdahl connections [--all]
370
370
  amdahl connect [<connector>] [--no-browser]
@@ -372,7 +372,7 @@ amdahl open [--no-browser]
372
372
  amdahl feedback <run-id> --evidence did_it_myself|reported_to_me|inferred
373
373
  [--outcome used|used_with_edits|not_used|unsure] [--sent-text <file>|-] [--comment <text>]
374
374
  amdahl sent <run-id> --provider <provider> --channel <channel> --sent-as returned|edited
375
- (--provider-send-id <id> | --recipient <email|url> | --recipient-key-hash <sha256>)
375
+ [--provider-send-id <id>] [--recipient <email|url> | --recipient-key-hash <sha256>]
376
376
  [--sent-at <time>] [--provider-campaign-id <id>] [--sequence-step <n>]
377
377
  [--provider-variant-id <id>] [--provider-lead-id <id>] [--thread-id <id>]
378
378
  [--sent-text <file>|-]
@@ -399,9 +399,20 @@ amdahl search "What objections came up in lost deals last quarter?"
399
399
  (`POST /search/query`, with `answer: true`). It prints a short answer, with
400
400
  each citation said in plain words, and the rows it came from under `From:`,
401
401
  at most five. With no written answer it prints up to ten rows. Each row is
402
- one line (its title, who and when) and up to three lines of its text, in 80
403
- columns. `--limit <n>` asks for at most `n` rows, and `--json` prints the
404
- server's whole answer, every row included.
402
+ one line (its title, who and when) and up to three lines of its quote (the
403
+ row's `content_preview`, or its text), in 80 columns. `--limit <n>` asks for
404
+ at most `n` rows, and `--json` prints the server's whole answer, every row
405
+ included.
406
+
407
+ When the server found rows but wrote no answer over them (`not_enough_data`),
408
+ the CLI says so plainly before the rows: "Amdahl couldn't answer that from
409
+ these rows. Try rephrasing the question, or run `amdahl connections` to see
410
+ whether your data is synced." The rows follow under `Rows it read:`. A
411
+ failed answer says it found rows but could not write an answer.
412
+
413
+ The CLI sends the question as you typed it. A window such as "last month" is
414
+ read by the server from the words (the previous calendar month, in UTC); the
415
+ request has no separate field for a time window.
405
416
 
406
417
  Research is something search starts, never a command of its own. Search
407
418
  hands a statistical or long question to a research job by itself, and
@@ -422,11 +433,11 @@ theme blocks print as plain text (an `<unverified>` figure is marked
422
433
  type this version does not know: both point to `--json`, where the whole block
423
434
  is. Starting research needs
424
435
  `conversations:write` and reading it back `conversations:read`. A CLI
425
- sign-in has neither, so the server runs the search instead and the CLI adds
426
- one line:
436
+ sign-in has neither, so the server runs the search instead. The CLI says so
437
+ first, before any row:
427
438
 
428
439
  ```
429
- A deeper answer needs a Customer agent key: amdahl keys create --name research --preset agent
440
+ Research needs a Customer agent key: amdahl keys create --name research --preset agent. Here is the search answer instead.
430
441
  ```
431
442
 
432
443
  A workspace with nothing connected answers with `setup_needed`: the CLI
@@ -459,7 +470,10 @@ console's add page,
459
470
  `<console>/<workspace>/settings/connections?add=1&connector=hubspot`. The
460
471
  `connector` only fills the search box on that page: you pick HubSpot there and
461
472
  sign in to it. With no connector it opens the add page with an empty search
462
- box. The CLI never creates a connection itself. An unknown connector exits 2
473
+ box. If that connector is already connected and healthy, the CLI says so
474
+ first ("HubSpot is already connected and healthy (last sync 2026-11-11).")
475
+ and prints the add page instead of opening it; one that needs attention gets
476
+ its own console page. The CLI never creates a connection itself. An unknown connector exits 2
463
477
  and lists the ones you can connect; one that is not offered yet exits 1 and
464
478
  says it is coming soon. It opens the browser only for a person at a terminal:
465
479
  stdin and stdout are terminals, `AMDAHL_NO_PROMPT=1` is not set, no agent runs
@@ -494,18 +508,24 @@ The list is a CSV file with a header row, or a JSONL file (`.jsonl` or
494
508
  `.ndjson`) with one batch item, a JSON object, per line. A CSV has a
495
509
  `message` column and may have `item_id`, `channel`, `evidence`,
496
510
  `on_unsupported`, `facts` and `rules`; facts and rules take one per line
497
- inside the cell. Any other column is refused by name before anything is
498
- sent, so a misspelled column is never dropped without a word (use JSONL for
499
- any other field a single optimize takes). The optimize flags (`--channel`,
511
+ inside the cell, and a channel is read in any case (`LinkedIn` is `linkedin`).
512
+ Any other column (a CRM export has many) is not sent: one
513
+ line names it, and it is copied to the results file in its place. Only a
514
+ missing `message` column, or a column Amdahl reads that appears twice (a
515
+ doubled `message`, say), is refused. A JSONL item takes any field a single
516
+ optimize takes, and the server refuses a key it does not know. The optimize flags (`--channel`,
500
517
  `--evidence`, `--on-unsupported`, `--tries`, `--context`) apply to every
501
518
  item that does not set its own; a row's facts and a `--context` file's rules
502
519
  and voice examples are both sent.
503
520
 
504
- The whole file is read and checked first. A header the CLI does not read, or a
505
- missing `message` column, says which headers it expects. Progress goes to
506
- stderr, in place at a terminal. When the batch completes, the results go next
507
- to the list: `drafts.csv` gets `drafts.optimized.csv` (every input column,
508
- then `optimized_message`, `status`, `what_changed`, `run_id` and `error`), and
521
+ The whole file is read and checked first. A missing `message` column says
522
+ which columns it reads. Progress goes to stderr, in place at a terminal: a
523
+ queued batch says it is queued, and each line after that says where it stands
524
+ once. When the batch completes, the results go next to the list: `drafts.csv`
525
+ gets `drafts.optimized.csv` (every input column in its original order, then
526
+ `optimized_message`, `status`, `summary`, `run_id` and `error`; a column of
527
+ yours with one of those names keeps it, and Amdahl's takes `amdahl_` in
528
+ front), and
509
529
  `x.jsonl` gets `x.optimized.jsonl` (one batch item per line, its `result`
510
530
  what a single optimize returns). With `--json`, the batch and every item go
511
531
  to stdout and no file is written. A failed item exits 1. A refused list
@@ -513,7 +533,10 @@ names the line of the first item the server refused.
513
533
 
514
534
  A spreadsheet runs a cell that starts with `=`, `+`, `-` or `@` as a formula,
515
535
  so in the results CSV such a cell (and one that starts with a tab or a
516
- carriage return) starts with a `'`. The CLI does not replace a results file
536
+ carriage return) starts with a `'`. That includes the cells copied through
537
+ from your own columns: a phone number such as `+1 555 0100` or a negative
538
+ number gets the `'` too, and you delete it to use the value. The `'` is the
539
+ guard, so it stays. The CLI does not replace a results file
517
540
  that already exists: it exits 2 before it queues anything, and `--force`
518
541
  replaces the file.
519
542
 
@@ -523,11 +546,70 @@ results go next to that list and the list must have as many messages as the
523
546
  batch has items (exit 2 otherwise), otherwise they go to `<id>.optimized.jsonl`
524
547
  here. `--no-wait` with `--batch` exits 2: it only queues a new list.
525
548
 
549
+ ### Send the list to your sequencer
550
+
551
+ ```sh
552
+ amdahl optimize --list drafts.csv --for lemlist --channel email
553
+ ```
554
+
555
+ `--for <tool>` (a CSV list only) also writes the messages Amdahl wrote as a
556
+ file that tool imports, next to the results, and prints three steps: where to
557
+ import it, what to map, and what to paste into the step. The file is
558
+ `<list>-<tool>.csv`: `drafts.csv` for lemlist gets `drafts-lemlist.csv`. The
559
+ console's Send to writes the same file for the same list.
560
+
561
+ ```
562
+ Results: drafts.optimized.csv
563
+ Import file for lemlist: drafts-lemlist.csv
564
+ 38 messages ready for lemlist. 2 left out: 1 failed, 1 with no email.
565
+ 1. Import: In your campaign, open Leads list > Import new leads > CSV.
566
+ 2. email and the name columns map on their own. amdahl_subject and
567
+ amdahl_message become custom variables.
568
+ 3. Paste into your step: Subject {{amdahl_subject}}, Body {{amdahl_message}}
569
+ ```
570
+
571
+ - **Tools:** `instantly`, `smartlead`, `lemlist`, `heyreach`, `reply_io`,
572
+ `apollo`, `outreach`, `salesloft`, `hubspot`, `lgm` (La Growth Machine),
573
+ `woodpecker`, and `other` for any tool with a CSV import. Gmail and Outlook
574
+ send from a mailbox, so they have no import file.
575
+ - **Channel:** a file is for one channel. A tool that takes one channel needs no
576
+ flag: HeyReach and La Growth Machine are LinkedIn, and the rest are email,
577
+ except lemlist, Reply.io and `other`, which take both. For those, add
578
+ `--channel email` or `--channel linkedin`, or put the channel in the list's
579
+ `channel` column. A list with both channels is refused.
580
+ - **Rows:** only leads Amdahl wrote. A lead that failed, needed input or was not
581
+ written is left out, and so is one with no email (no LinkedIn URL for
582
+ LinkedIn; HeyReach also needs a first and a last name). The line after the
583
+ count says how many and why.
584
+ - **Columns:** your list's `email`, `first_name`, `last_name`, `company` and
585
+ `linkedin_url`, found by header in any common spelling and named as the tool
586
+ names them (lemlist `firstName`, `companyName`; Instantly and Smartlead
587
+ `company_name`), then `amdahl_subject` and `amdahl_message` (just
588
+ `amdahl_message` for LinkedIn), then every other column of your list in its
589
+ order, as lowercase snake_case. The column the message came from (the first
590
+ whose header names a draft, such as `message`, `draft` or `body`) and the columns Amdahl
591
+ reads itself (`message`, `channel`, `evidence`, `on_unsupported`, `facts`,
592
+ `rules` and `item_id`, matched on the snake_case header, so `Item ID` too)
593
+ are not in the file. Your tool imports a value as it is, so no cell gets the `'`
594
+ guard that the results file has: a `-20% off` subject or a `+1 415...` phone
595
+ number arrives as written. Line breaks are CRLF, and there is no byte-order
596
+ mark.
597
+ - **Checked first:** before anything is queued, a list the tool cannot import
598
+ exits 2: no email column for an email file, no LinkedIn URL column for a
599
+ LinkedIn one, a JSONL list (the file keeps your columns), a file that already
600
+ exists (`--force` replaces it). `--no-wait` queues the batch, and the line it
601
+ prints to pick it up carries `--for` and `--channel`.
602
+ - **Under `--json`:** the file is still written, no steps are printed, and the
603
+ batch gains `send_to`: `tool`, `channel`, `file` (null when no lead was
604
+ ready), `ready`, `left_out` (`failed`, `not_written`, `needs_input`,
605
+ `no_contact`) and `over_limit`. With no lead ready, no file is written and
606
+ the exit is 1.
607
+
526
608
  ## After you send
527
609
 
528
610
  ```sh
529
611
  amdahl feedback <run-id> --evidence did_it_myself --outcome used_with_edits --sent-text sent.txt
530
- amdahl sent <run-id> --provider gmail --channel email --sent-as edited --provider-send-id "<msg-id@mail.gmail.com>"
612
+ amdahl sent <run-id> --provider gmail --channel email --sent-as edited --sent-text sent.txt --provider-send-id "<msg-id@mail.gmail.com>"
531
613
  ```
532
614
 
533
615
  `feedback` says what happened to the message one optimize run returned
@@ -543,16 +625,23 @@ stores two.
543
625
  (`POST /messages/report_send`). A report from a CLI sign-in is stored as
544
626
  `reported_via: cli`. The flags are the report's fields: `--provider`
545
627
  (`lemlist`, `instantly`, `outreach`, `salesloft`, `hubspot`, `salesforce`,
546
- `gmail`, `heyreach`, `apollo`, `linkedin_manual`, `other`), `--channel`
547
- (`email`, `linkedin_invite`, `linkedin_message`, `linkedin_inmail`),
628
+ `gmail`, `heyreach`, `apollo`, `linkedin_manual`, `other`; the server's
629
+ list, so use `other` for a sender that is not on it), `--channel` (`email`,
630
+ `linkedin_invite`, `linkedin_message`, `linkedin_inmail`; `linkedin` is
631
+ accepted and means `linkedin_message`, as in `amdahl optimize --channel`),
548
632
  `--sent-as` (`returned` or `edited`) and `--sent-at` (a time with a time
549
- zone; the current time when left out), then the optional ids.
550
-
551
- The server stores a report only when it can tell which send it is, so one of
552
- `--provider-send-id`, `--recipient` (an email address, or a LinkedIn profile
553
- URL for a LinkedIn channel) or `--recipient-key-hash` (a SHA-256) is required.
554
- A campaign, a lead and a step are not enough. Without one, the CLI exits 2
555
- before it sends anything. The id is the provider's own: for gmail, the
633
+ zone; the current time when left out), then the optional ids. `--sent-as
634
+ edited` needs `--sent-text`, the message as you sent it (Amdahl keeps only a
635
+ hash of it), and the file must not be empty. That is the CLI's rule, not the
636
+ server's. A run id given twice, or two run ids, exits 2.
637
+
638
+ The server stores a report only when it can tell which send it is, so you give
639
+ at least one of `--provider-send-id` and a recipient: `--recipient` (an email
640
+ address, or a LinkedIn profile URL for a LinkedIn channel) or
641
+ `--recipient-key-hash` (a SHA-256), never both. A send id with a recipient is
642
+ fine, and the server then counts a report with and one without the id as one
643
+ send. A campaign, a lead and a step are not enough. Without a send id or a
644
+ recipient, the CLI exits 2 before it sends anything. The id is the provider's own: for gmail, the
556
645
  `Message-ID` header with its angle brackets; never an email address (give that
557
646
  as `--recipient`; Amdahl stores only a hash of it). The command reports one
558
647
  send, so give one run id. To report many at once, call
@@ -580,6 +669,39 @@ lines; `--json` never carries them.
580
669
  a connection that needs attention, its console page; a used-up quota, the
581
670
  day it resets; a role too low, ask a workspace admin.
582
671
  - Bare `amdahl`, signed in, with every tool set up: what to try here.
672
+ - After an optimize (one file, a pasted message, or a list) a line says what
673
+ to do once the message is sent: two lines, each a command to paste, with
674
+ the run's own id and the flags it needs as placeholders (`amdahl sent
675
+ <run-id> --provider <provider> --channel <channel> --sent-as returned
676
+ --provider-send-id <id>`, then `amdahl feedback <run-id> --evidence
677
+ did_it_myself --outcome used`). A list prints the first run id of its
678
+ results. Replace each `<placeholder>` before you run it.
679
+ - `amdahl optimize` no longer prints the server's `what_changed` line. It is
680
+ the same sentence on every run. It is still in `--json`.
681
+
682
+ ## Help and errors
683
+
684
+ `amdahl <command> --help` (also `-h`, or `amdahl help <command>`) prints that
685
+ command's usage and nothing else: its flags, what it prints, and its exit
686
+ codes where they matter. `amdahl keys --help` lists the `keys` commands. For
687
+ `optimize --json`, the help says where the message is: `.result.message`, and
688
+ the run id is `.result.run_id`.
689
+
690
+ Three failures print a plain sentence each. Only the sentence changed from
691
+ 0.4.0: the `--json` error `code` is still the server's (or `http_<status>`) and
692
+ its `hint` is the same, because scripts and agents branch on them.
693
+
694
+ - A credential that lacks a scope says what it cannot do and what to use: `This
695
+ credential cannot optimize. Use a Customer agent key, or run \`amdahl
696
+ login\` again.` (exit 4). It never prints the server's `Missing required
697
+ scope(s)`.
698
+ - A key the server will not take (revoked or expired): `This key isn't valid
699
+ (revoked or expired).` and the hint, `Run amdahl login, or check
700
+ AMDAHL_KEY.` (exit 3). An OAuth access token in `AMDAHL_ACCESS_TOKEN` says
701
+ `This token isn't valid (revoked or expired).` A saved sign-in the server
702
+ will not take: `Your sign-in is no longer valid.`
703
+ - A file that is not there: `Can't find <file>.` (exit 2), for a draft, a
704
+ list, `--context` and `--sent-text`.
583
705
 
584
706
  ## Skills
585
707
 
package/bin/amdahl.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // bin/amdahl.js
3
3
  //
4
4
  // The `amdahl` command. It checks the Node.js version before it loads the
5
- // CLI: dist/amdahl.js is built for Node 20, and an older Node fails to parse
5
+ // CLI: dist/amdahl.js is built for Node 22, and an older Node fails to parse
6
6
  // it or crashes in it, while npx only warns about `engines`. So this file
7
7
  // uses no syntax newer than Node 14 (no `??=`, no top-level await, no class
8
8
  // fields), and an old Node can run it far enough to say what to do.
@@ -10,9 +10,9 @@
10
10
  var version = process.versions.node
11
11
  var major = parseInt(version.split('.')[0], 10)
12
12
 
13
- if (!(major >= 20)) {
13
+ if (!(major >= 22)) {
14
14
  process.stderr.write(
15
- 'amdahl needs Node.js 20 or later. This computer has Node.js ' +
15
+ 'amdahl needs Node.js 22 or later. This computer has Node.js ' +
16
16
  version +
17
17
  '.\nInstall a newer one from https://nodejs.org/en/download, then run the same command again.\n'
18
18
  )