@amdahl/cli 0.3.7 → 0.4.1

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
@@ -1,9 +1,12 @@
1
1
  # @amdahl/cli
2
2
 
3
3
  The `amdahl` command line. It signs you in with your console account, checks
4
- that a workspace can optimize, rewrites messages, manages API keys (each one
5
- approved in the console), points MCP clients at Amdahl, and installs skills
6
- from the library at https://amdahl.ai/skills.
4
+ that a workspace can optimize, rewrites messages one at a time or a whole
5
+ list at once, answers questions about your customers, calls and deals, shows
6
+ and adds connected sources, records what happened to a message after you
7
+ sent it, manages API keys (each one approved in the console), points MCP
8
+ clients at Amdahl, and installs skills from the library at
9
+ https://amdahl.ai/skills.
7
10
 
8
11
  ## Using the Claude or ChatGPT app? You do not need this CLI
9
12
 
@@ -40,7 +43,8 @@ Full steps: https://amdahl.ai/connect
40
43
 
41
44
  ## Install
42
45
 
43
- Needs Node.js 20 or later. On an older Node, `amdahl` stops before it does
46
+ Needs Node.js 22 or later. Node.js 20 reached end of life in April 2026 and
47
+ gets no security fixes. On a Node older than 22, `amdahl` stops before it does
44
48
  anything, names the version it found, and links the Node.js download page.
45
49
 
46
50
  ```sh
@@ -203,6 +207,18 @@ If not: quit and reopen Claude Code, type /mcp, pick amdahl, sign in.
203
207
  Want to try it here instead? Run: npx -y amdahl optimize
204
208
  ```
205
209
 
210
+ If you are already signed in here (a saved `amdahl login` for this API host,
211
+ or a key in `--api-key` or the environment), the last line is replaced by
212
+ what to do next in this terminal:
213
+
214
+ ```
215
+ Try it here: amdahl optimize or amdahl search "your question"
216
+ Add data: amdahl connect, see it all: amdahl open
217
+ ```
218
+
219
+ It still reads nothing from the network. An agent's run and a run with no
220
+ terminal print exactly what they did before.
221
+
206
222
  An old copy of the Amdahl skill is brought up to date on the way. A tool it
207
223
  finds that does not point at Amdahl yet brings the menu back.
208
224
  `npx -y amdahl --verbose` also prints the files it wrote, the profile and
@@ -302,6 +318,13 @@ amdahl login # sign in through the browser with your console acc
302
318
  amdahl whoami # the account, workspace and role you are signed in as
303
319
  amdahl status # can this workspace optimize, and what blocks it
304
320
  amdahl optimize draft.md # rewrite one message (or pipe it in with -, or run it bare and paste)
321
+ amdahl optimize --list drafts.csv # rewrite a whole list in one batch
322
+ amdahl search "what objections came up in lost deals?" # ask about your customers
323
+ amdahl connections # what is connected, and whether it is healthy
324
+ amdahl connect hubspot # open the console's add page, to add a CRM or call recorder
325
+ amdahl open # open this workspace in the console
326
+ amdahl feedback <run-id> --evidence did_it_myself --outcome used # what happened to it
327
+ amdahl sent <run-id> --provider gmail --channel email --sent-as returned --provider-send-id "<msg-id@mail>" # it went out
305
328
  amdahl install claude-code # connect an AI client and add the Amdahl skill (also codex, cursor)
306
329
  amdahl skills add <publisher>/<skill> # add a skill from amdahl.ai/skills to this project
307
330
  ```
@@ -340,6 +363,18 @@ amdahl auth token
340
363
  amdahl status
341
364
  amdahl optimize [<file>... | -] [--channel email|linkedin] [--evidence off|workspace]
342
365
  [--on-unsupported flag|remove] [--tries] [--context <file.json>]
366
+ amdahl optimize --list <file.csv|file.jsonl> [--no-wait] [--batch <id>] [--force] [the optimize flags]
367
+ amdahl search "<question>" [--research] [--limit <n>]
368
+ amdahl connections [--all]
369
+ amdahl connect [<connector>] [--no-browser]
370
+ amdahl open [--no-browser]
371
+ amdahl feedback <run-id> --evidence did_it_myself|reported_to_me|inferred
372
+ [--outcome used|used_with_edits|not_used|unsure] [--sent-text <file>|-] [--comment <text>]
373
+ amdahl sent <run-id> --provider <provider> --channel <channel> --sent-as returned|edited
374
+ [--provider-send-id <id>] [--recipient <email|url> | --recipient-key-hash <sha256>]
375
+ [--sent-at <time>] [--provider-campaign-id <id>] [--sequence-step <n>]
376
+ [--provider-variant-id <id>] [--provider-lead-id <id>] [--thread-id <id>]
377
+ [--sent-text <file>|-]
343
378
  amdahl keys create --name <name> [--preset read-only|agent|internal|admin] [--expires 30d|90d|365d] [--no-browser]
344
379
  amdahl keys list
345
380
  amdahl keys revoke <id|prefix> [--yes] [--no-browser]
@@ -353,6 +388,260 @@ Global flags: `--profile`, `--api-url`, `--api-key`, `--json`, `--no-color`,
353
388
  stderr. An unknown option prints `Unknown option --foo.` and `Run \`amdahl
354
389
  --help\`.` (exit 2).
355
390
 
391
+ ## Search
392
+
393
+ ```sh
394
+ amdahl search "What objections came up in lost deals last quarter?"
395
+ ```
396
+
397
+ `search` asks one question about the workspace's customers, calls and deals
398
+ (`POST /search/query`, with `answer: true`). It prints a short answer, with
399
+ each citation said in plain words, and the rows it came from under `From:`,
400
+ at most five. With no written answer it prints up to ten rows. Each row is
401
+ one line (its title, who and when) and up to three lines of its quote (the
402
+ row's `content_preview`, or its text), in 80 columns. `--limit <n>` asks for
403
+ at most `n` rows, and `--json` prints the server's whole answer, every row
404
+ included.
405
+
406
+ When the server found rows but wrote no answer over them (`not_enough_data`),
407
+ the CLI says so plainly before the rows: "Amdahl couldn't answer that from
408
+ these rows. Try rephrasing the question, or run `amdahl connections` to see
409
+ whether your data is synced." The rows follow under `Rows it read:`. A
410
+ failed answer says it found rows but could not write an answer.
411
+
412
+ The CLI sends the question as you typed it. A window such as "last month" is
413
+ read by the server from the words (the previous calendar month, in UTC); the
414
+ request has no separate field for a time window.
415
+
416
+ Research is something search starts, never a command of its own. Search
417
+ hands a statistical or long question to a research job by itself, and
418
+ `--research` asks it to (`route: "research"`). The CLI then waits for the
419
+ job (`GET /research/:id`, 45 seconds a read, up to 15 minutes) and prints its
420
+ cited answer, then what the answer rests on under `Details:` (its
421
+ `content_blocks`, in the order the job presented them), then up to three
422
+ questions to ask next.
423
+
424
+ Each table prints as a plain ASCII table that fits 80 columns: a long cell
425
+ wraps (a column is narrowed to its longest word first, so an id or a timestamp
426
+ stays whole), a cell past four lines is cut with `...`, a word wider than its
427
+ column is split with a `-`, and a table with more columns than 80 columns hold
428
+ prints each row as `label: value` lines, so no column is dropped. A table
429
+ prints at most 25 rows and counts the rest. Text, callout, citation, metric and
430
+ theme blocks print as plain text (an `<unverified>` figure is marked
431
+ `(unverified)`). A chart prints one line that says so, and so does any block
432
+ type this version does not know: both point to `--json`, where the whole block
433
+ is. Starting research needs
434
+ `conversations:write` and reading it back `conversations:read`. A CLI
435
+ sign-in has neither, so the server runs the search instead. The CLI says so
436
+ first, before any row:
437
+
438
+ ```
439
+ Research needs a Customer agent key: amdahl keys create --name research --preset agent. Here is the search answer instead.
440
+ ```
441
+
442
+ A workspace with nothing connected answers with `setup_needed`: the CLI
443
+ prints its message, which carries the console link, then
444
+ `Connect your CRM or call recorder: amdahl connect`. With no rows, it prints
445
+ the server's one line on how fresh the data is, or `Nothing matched.`
446
+
447
+ ## Connections
448
+
449
+ ```sh
450
+ amdahl connections
451
+ ```
452
+
453
+ ```
454
+ HubSpot crm healthy synced 2026-11-11
455
+ Gong (Sales calls) calls needs sign-in never synced
456
+ Sign in to it again: https://console.amdahl.ai/acme/connections/c1
457
+ ```
458
+
459
+ `connections` lists what the workspace has connected (`GET /connections`),
460
+ each with its health in words and its last sync. Under the list, one line
461
+ says where to fix what needs attention: the one connection's console page,
462
+ or the Connections page when there are several. `--all` also lists
463
+ disconnected and never-synced sources. With nothing connected it says so and
464
+ names `amdahl connect`.
465
+
466
+ `amdahl connect hubspot` checks the connector against the catalog
467
+ (`GET /connections/catalog`; a type or a name, in any case), then opens the
468
+ console's add page,
469
+ `<console>/<workspace>/settings/connections?add=1&connector=hubspot`. The
470
+ `connector` only fills the search box on that page: you pick HubSpot there and
471
+ sign in to it. With no connector it opens the add page with an empty search
472
+ box. If that connector is already connected and healthy, the CLI says so
473
+ first ("HubSpot is already connected and healthy (last sync 2026-11-11).")
474
+ and prints the add page instead of opening it; one that needs attention gets
475
+ its own console page. The CLI never creates a connection itself. An unknown connector exits 2
476
+ and lists the ones you can connect; one that is not offered yet exits 1 and
477
+ says it is coming soon. It opens the browser only for a person at a terminal:
478
+ stdin and stdout are terminals, `AMDAHL_NO_PROMPT=1` is not set, no agent runs
479
+ it (`CLAUDECODE`, `CURSOR_AGENT`), it is not CI and there is no `--json`. So
480
+ `amdahl connect hubspot | cat` opens nothing. Everywhere else, with
481
+ `--no-browser`, or when the browser does not open, it prints the link on
482
+ stdout instead (under `--json`, as `url` with `opened: false`).
483
+
484
+ `amdahl open` opens this workspace in the console (the console home when you
485
+ are not signed in), on the same terms. `--no-browser` prints the link.
486
+
487
+ ## Optimize a list
488
+
489
+ ```sh
490
+ amdahl optimize --list drafts.csv
491
+ ```
492
+
493
+ ```
494
+ Queued 40 messages as batch 7c1e....
495
+ Ctrl+C stops waiting, not the batch. To pick it up later: amdahl optimize --list drafts.csv --batch 7c1e...
496
+ 12 of 40 done, about 3 min left
497
+ 40 of 40 messages optimized.
498
+ Results: drafts.optimized.csv
499
+ ```
500
+
501
+ `--list` sends a whole list as one batch (`POST /messages/optimize/batch`),
502
+ which the server runs on spare capacity and never refuses as busy, then
503
+ reads it back (`GET /messages/optimize/batches/:id`) until it completes. A
504
+ file given without `--list` is still one message, as before.
505
+
506
+ The list is a CSV file with a header row, or a JSONL file (`.jsonl` or
507
+ `.ndjson`) with one batch item, a JSON object, per line. A CSV has a
508
+ `message` column and may have `item_id`, `channel`, `evidence`,
509
+ `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
511
+ line names it, and it is copied to the results file in its place. Only a
512
+ missing `message` column, or a column Amdahl reads that appears twice (a
513
+ doubled `message`, say), is refused. A JSONL item takes any field a single
514
+ optimize takes, and the server refuses a key it does not know. The optimize flags (`--channel`,
515
+ `--evidence`, `--on-unsupported`, `--tries`, `--context`) apply to every
516
+ item that does not set its own; a row's facts and a `--context` file's rules
517
+ and voice examples are both sent.
518
+
519
+ The whole file is read and checked first. A missing `message` column says
520
+ which columns it reads. Progress goes to stderr, in place at a terminal: a
521
+ queued batch says it is queued, and each line after that says where it stands
522
+ once. When the batch completes, the results go next to the list: `drafts.csv`
523
+ gets `drafts.optimized.csv` (every input column in its original order, then
524
+ `optimized_message`, `status`, `summary`, `run_id` and `error`; a column of
525
+ yours with one of those names keeps it, and Amdahl's takes `amdahl_` in
526
+ front), and
527
+ `x.jsonl` gets `x.optimized.jsonl` (one batch item per line, its `result`
528
+ what a single optimize returns). With `--json`, the batch and every item go
529
+ to stdout and no file is written. A failed item exits 1. A refused list
530
+ names the line of the first item the server refused.
531
+
532
+ A spreadsheet runs a cell that starts with `=`, `+`, `-` or `@` as a formula,
533
+ so in the results CSV such a cell (and one that starts with a tab or a
534
+ carriage return) starts with a `'`. That includes the cells copied through
535
+ from your own columns: a phone number such as `+1 555 0100` or a negative
536
+ number gets the `'` too, and you delete it to use the value. The `'` is the
537
+ guard, so it stays. The CLI does not replace a results file
538
+ that already exists: it exits 2 before it queues anything, and `--force`
539
+ replaces the file.
540
+
541
+ `--no-wait` queues the batch, prints its id and the command that picks it up,
542
+ and exits. `--batch <id>` picks up a batch queued earlier; with `--list`, the
543
+ results go next to that list and the list must have as many messages as the
544
+ batch has items (exit 2 otherwise), otherwise they go to `<id>.optimized.jsonl`
545
+ here. `--no-wait` with `--batch` exits 2: it only queues a new list.
546
+
547
+ ## After you send
548
+
549
+ ```sh
550
+ amdahl feedback <run-id> --evidence did_it_myself --outcome used_with_edits --sent-text sent.txt
551
+ amdahl sent <run-id> --provider gmail --channel email --sent-as edited --sent-text sent.txt --provider-send-id "<msg-id@mail.gmail.com>"
552
+ ```
553
+
554
+ `feedback` says what happened to the message one optimize run returned
555
+ (`POST /messages/optimize/runs/:id/feedback`). `--evidence` is required:
556
+ `did_it_myself`, `reported_to_me` or `inferred`. Send at least one of
557
+ `--outcome` (`used`, `used_with_edits`, `not_used`, `unsure`), `--comment`
558
+ (the sender's own words) and `--sent-text` (a file with the text as sent, or
559
+ `-` for stdin). Amdahl learns the sender's preferences from an edit, unless
560
+ the evidence is `inferred`. Each call stores one event, so running it twice
561
+ stores two.
562
+
563
+ `sent` is for the system that sent the message: it reports that it went out
564
+ (`POST /messages/report_send`). A report from a CLI sign-in is stored as
565
+ `reported_via: cli`. The flags are the report's fields: `--provider`
566
+ (`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`,
569
+ `linkedin_invite`, `linkedin_message`, `linkedin_inmail`; `linkedin` is
570
+ accepted and means `linkedin_message`, as in `amdahl optimize --channel`),
571
+ `--sent-as` (`returned` or `edited`) and `--sent-at` (a time with a time
572
+ zone; the current time when left out), then the optional ids. `--sent-as
573
+ edited` needs `--sent-text`, the message as you sent it (Amdahl keeps only a
574
+ hash of it), and the file must not be empty. That is the CLI's rule, not the
575
+ server's. A run id given twice, or two run ids, exits 2.
576
+
577
+ The server stores a report only when it can tell which send it is, so you give
578
+ at least one of `--provider-send-id` and a recipient: `--recipient` (an email
579
+ address, or a LinkedIn profile URL for a LinkedIn channel) or
580
+ `--recipient-key-hash` (a SHA-256), never both. A send id with a recipient is
581
+ fine, and the server then counts a report with and one without the id as one
582
+ send. A campaign, a lead and a step are not enough. Without a send id or a
583
+ recipient, the CLI exits 2 before it sends anything. The id is the provider's own: for gmail, the
584
+ `Message-ID` header with its angle brackets; never an email address (give that
585
+ as `--recipient`; Amdahl stores only a hash of it). The command reports one
586
+ send, so give one run id. To report many at once, call
587
+ `POST /messages/report_send`, which takes up to 100 items. Give `--recipient`
588
+ or `--recipient-key-hash`, not both: the server refuses the pair as
589
+ `invalid_recipient`, so the CLI exits 2 first. `--sent-at` needs an uppercase
590
+ `T` and `Z`, as the server does. The CLI checks each value against the
591
+ server's limits first, and a mistake is a usage error that names the flag. It
592
+ counts a text's length as the server does, in UTF-16 units, so an emoji counts
593
+ as two. A send the server did not store prints why and exits 1.
594
+
595
+ ## Next steps
596
+
597
+ Human output names the next step where there is one, in at most two extra
598
+ lines; `--json` never carries them.
599
+
600
+ - `amdahl optimize` on a workspace with no data connected ends with
601
+ `Connect calls or a CRM so Amdahl checks claims against them: amdahl connect`.
602
+ The CLI asks `GET /setup/status` beside the first rewrite, so it adds no
603
+ wait, and says nothing when it cannot tell. Only a person at a terminal
604
+ gets the line and the request (the same test as `optimize` with no file): a
605
+ pipe, an agent, CI, `AMDAHL_NO_PROMPT=1` and `--json` make exactly the
606
+ requests they made before.
607
+ - `amdahl status` gives each blocked item its fix: no data, `amdahl connect`;
608
+ a connection that needs attention, its console page; a used-up quota, the
609
+ day it resets; a role too low, ask a workspace admin.
610
+ - Bare `amdahl`, signed in, with every tool set up: what to try here.
611
+ - After an optimize (one file, a pasted message, or a list) a line says what
612
+ to do once the message is sent: two lines, each a command to paste, with
613
+ the run's own id and the flags it needs as placeholders (`amdahl sent
614
+ <run-id> --provider <provider> --channel <channel> --sent-as returned
615
+ --provider-send-id <id>`, then `amdahl feedback <run-id> --evidence
616
+ did_it_myself --outcome used`). A list prints the first run id of its
617
+ results. Replace each `<placeholder>` before you run it.
618
+ - `amdahl optimize` no longer prints the server's `what_changed` line. It is
619
+ the same sentence on every run. It is still in `--json`.
620
+
621
+ ## Help and errors
622
+
623
+ `amdahl <command> --help` (also `-h`, or `amdahl help <command>`) prints that
624
+ command's usage and nothing else: its flags, what it prints, and its exit
625
+ codes where they matter. `amdahl keys --help` lists the `keys` commands. For
626
+ `optimize --json`, the help says where the message is: `.result.message`, and
627
+ the run id is `.result.run_id`.
628
+
629
+ Three failures print a plain sentence each. Only the sentence changed from
630
+ 0.4.0: the `--json` error `code` is still the server's (or `http_<status>`) and
631
+ its `hint` is the same, because scripts and agents branch on them.
632
+
633
+ - A credential that lacks a scope says what it cannot do and what to use: `This
634
+ credential cannot optimize. Use a Customer agent key, or run \`amdahl
635
+ login\` again.` (exit 4). It never prints the server's `Missing required
636
+ scope(s)`.
637
+ - A key the server will not take (revoked or expired): `This key isn't valid
638
+ (revoked or expired).` and the hint, `Run amdahl login, or check
639
+ AMDAHL_KEY.` (exit 3). An OAuth access token in `AMDAHL_ACCESS_TOKEN` says
640
+ `This token isn't valid (revoked or expired).` A saved sign-in the server
641
+ will not take: `Your sign-in is no longer valid.`
642
+ - A file that is not there: `Can't find <file>.` (exit 2), for a draft, a
643
+ list, `--context` and `--sent-text`.
644
+
356
645
  ## Skills
357
646
 
358
647
  `skills add` installs one skill from the library at https://amdahl.ai/skills.
@@ -569,10 +858,10 @@ code shown in the terminal; then the key is printed once and stored nowhere.
569
858
  | code | meaning |
570
859
  |---|---|
571
860
  | 0 | ok |
572
- | 1 | general (`login_timeout`, `state_mismatch`, optimize `ok: false`, a `skills add` refusal, `invalid_config`, bare `amdahl` with no terminal that found no client) |
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) |
573
862
  | 2 | usage (also bare `amdahl` in CI, with `AMDAHL_NO_PROMPT=1` or with `--json`) |
574
863
  | 3 | not signed in (401, `invalid_grant`, no credential) |
575
- | 4 | forbidden (403, `oauth_required`, `cli_client_required`, `not_admin`) |
864
+ | 4 | forbidden (403, `oauth_required`, `cli_client_required`, `not_admin`, `missing_scope`; also research that started but this credential cannot read) |
576
865
  | 5 | no workspace |
577
866
  | 6 | rate limited or quota (429) |
578
867
  | 7 | approval denied, expired or locked out |
@@ -582,7 +871,7 @@ code shown in the terminal; then the key is printed once and stored nowhere.
582
871
 
583
872
  ## Development
584
873
 
585
- Needs Node.js 20 or later and pnpm 9 (`corepack enable` picks up the version
874
+ Needs Node.js 22 or later and pnpm 9 (`corepack enable` picks up the version
586
875
  pinned in `package.json`).
587
876
 
588
877
  ```sh
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
  )