@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 +153 -31
- package/bin/amdahl.js +3 -3
- package/dist/amdahl.js +1257 -410
- package/package.json +1 -1
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.
|
|
48
|
-
|
|
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
|
-
|
|
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
|
|
403
|
-
columns. `--limit <n>` asks for
|
|
404
|
-
server's whole answer, every row
|
|
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
|
|
426
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
498
|
-
|
|
499
|
-
|
|
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
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
to the list: `drafts.csv`
|
|
508
|
-
|
|
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 `'`.
|
|
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
|
|
547
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
|
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 >=
|
|
13
|
+
if (!(major >= 22)) {
|
|
14
14
|
process.stderr.write(
|
|
15
|
-
'amdahl needs 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
|
)
|