Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ available yet: the backend refuses them). Since 0.2.1, unknown commands and subc
`unknown_command` before any credential lookup. Published 0.1.0 through 0.2.3
do not include `login`, `onboard`, `new`, `doctor` or `dev`. Browser login and the
agent onboarding CLI live in the separate `chert` CLI from `@trychert/facetime`.
Unreleased: `chert-sdk onboard` for a Mac, see [setup](docs/SETUP.md#automated-onboarding-on-a-mac).

### Custom video sources (not available yet)

Expand Down
46 changes: 43 additions & 3 deletions docs/SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,51 @@ Coding agents can read the same material from [`llms.txt`](https://github.com/ch
| Migrate from another iMessage provider SDK | `chert-sdk adopt` (ships in `@trychert/sdk`) |

The SDKs and the FaceTime agent CLI are separate packages with separate commands.
The SDK's `chert-sdk` CLI has no `login`, `onboard`, `new` or `dev` command.
The SDK's `chert-sdk` CLI has no `login`, `new` or `dev` command. On a Mac,
[`chert-sdk onboard`](#automated-onboarding-on-a-mac) gets you started without a browser.

For Better Auth, see the [phone transport guide](BETTER_AUTH.md): source preview,
not included in npm 0.2.3. Existing email auth stays enabled alongside phone.

## Automated onboarding on a Mac

Not released yet: this needs the next `@trychert/sdk` and server support that has
not shipped.

```sh
chert-sdk onboard
```

1. Reads the phone numbers signed in to Messages. With several, it asks which to
use, or all. It reads Messages' aliases only, so a number listed only in the
Apple ID's vetted aliases is not offered.
2. After you confirm, it sends a one-time code to Chert from your Messages app for
each number. The first run asks macOS for permission to control Messages. Chert
accepts a code only from the number it was issued for, so reading a number from
your Mac is never itself proof.
3. Saves the sign-in where the `chert` CLI reads it (`~/.chert`, or `CHERT_HOME`).
4. Sets up shared access and your trial, verifies the number for calls with one more
code, then requests the FaceTime demo. It waits in line if the line is busy. An
accepted request does not confirm audio or video worked.
5. In a project (a language manifest or `.git`), it prints the install command and a
prompt for your coding agent, and writes `CHERT_FACETIME_TOKEN` and
`CHERT_PROJECT_ID` to `.env.local` only if git ignores that file and it does not
already set them differently. It never edits source files. Elsewhere, it offers to
install the `chert` CLI from `@trychert/facetime`.

Messages sends from the number under **Settings > iMessage > Start New Conversations
From**, and a script cannot choose it. A chosen number sent from another number does
not verify, and the command says so. Change that setting and rerun to add it. With
two numbers, the demo calls the first one verified.

| Flag | Effect |
| --- | --- |
| `--numbers +A,+B` | Use these numbers, by value or list position, or `all`. They must be on this Mac. Counts as confirmation |
| `--yes` | Use every number without asking |
| `--json` | One JSON line per step with a stable `code`. Never prompts; without a choice it stops with `numbers_required` and masked numbers |
| `--skip-demo` | Stop before the demo call |
| `--no-wire` | Skip the project or CLI step |

## 1. Install

JavaScript / TypeScript (Node 22.12+, current Bun, or Deno 2):
Expand Down Expand Up @@ -55,8 +95,8 @@ Credentials are per service and are not interchangeable.
| `CHERT_PROJECT_ID` | FaceTime (required), iMessage (context only) | Your project UUID. The dashboard does not display it yet: install the agent CLI (`npm install -g @trychert/facetime@beta`), run `chert login`, then `chert whoami --json` and copy `project_id`. |

Both credentials need an account with Chert. A browser is required once, to
sign in and create the key; nothing in this guide can create an account or a key
from the terminal.
sign in and create the key, unless you use `chert-sdk onboard` on a Mac, which
creates a CLI login (not a project API key) for FaceTime.

A project API key works for lines, workers, calls and assistants within the
scopes you give it. It is not a person, so it cannot read a signed-in identity:
Expand Down
4 changes: 2 additions & 2 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Rules for coding agents:
- FaceTime calls can carry audio and video (calls can also be audio-only). Video needs a line with `supports_video`, a FaceTime Video call, and an agent that publishes a camera track.
- Direct audio transport (`media_transport: "direct"` with `media_url`/`media_token` on customer-managed calls, no LiveKit) is not available yet; the SDKs send it but the hosted backend does not accept it. See FaceTime notes.
- Custom video (WHIP/WHEP), living services, screen sharing and call video composition are not available yet (PRs #17, #18, #25, #29); do not build on them. All four ship in the package (composition since 0.2.3), but the hosted backend does not serve them (`404 endpoint_unavailable`).
- Credentials need a browser once: a FaceTime project API key comes from the dashboard's Developer tab, the project UUID from `chert whoami --json`, and billing only works in the browser today.
- `chert-sdk` has no `login`, `onboard`, `new` or `dev` command; those belong to the `chert` CLI from `@trychert/facetime`.
- Without `chert-sdk onboard`, credentials need a browser once: a FaceTime project API key comes from the dashboard's Developer tab, the project UUID from `chert whoami --json`, and billing only works in the browser today.
- `chert-sdk onboard` (macOS, unreleased) signs in with the Mac's own number by sending a one-time code from Messages, sets up the trial and requests the FaceTime demo. In a project it prints a `wire_in` handoff with a prompt; elsewhere it sets up the `chert` CLI. With `--json` and no choice it stops with `numbers_required`: ask the user which numbers, then pass `--numbers`. `chert-sdk` has no `login`, `new` or `dev` command; those belong to the `chert` CLI from `@trychert/facetime`.

## Docs

Expand Down
2 changes: 1 addition & 1 deletion packages/unified/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ test('CLI preserves a safe SDK error code without exposing server text or respon
});

test('CLI reports unknown commands as such, even without credentials', async () => {
for (const command of [['login'], ['onboard'], ['facetim'], ['facetime', 'statsu'], ['facetime', 'lines', 'extra'], ['imessage', 'sned'], ['context', 'extra'], ['context', '--offline', 'extra'], ['context', '--bogus'], ['billing'], ['billing', 'refund'], ['billing', 'status', 'extra']]) {
for (const command of [['login'], ['facetim'], ['facetime', 'statsu'], ['facetime', 'lines', 'extra'], ['imessage', 'sned'], ['context', 'extra'], ['context', '--offline', 'extra'], ['context', '--bogus'], ['billing'], ['billing', 'refund'], ['billing', 'status', 'extra']]) {
const env = { ...process.env }; delete env.CHERT_PROJECT_ID;
const child = Bun.spawn(['node', resolve(import.meta.dir, '../../dist/unified/cli.js'), ...command], { env, stdout: 'pipe', stderr: 'pipe' });
expect(await child.exited).toBe(1);
Expand Down
16 changes: 13 additions & 3 deletions packages/unified/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { main as adopt } from '../adopt/src/main.js';
import { SDK_VERSION } from '../adopt/src/providers.js';
import { runOnboard } from './onboard.js';

const DOCS = 'https://github.com/cherthq/sdk/blob/main';

const help = `chert-sdk — unified Chert SDK CLI
chert-sdk onboard [--numbers +A,+B|1,2|all] [--yes] [--json] [--skip-demo] [--no-wire]
chert-sdk imessage send --phone +COUNTRYNUMBER --body TEXT --idempotency-key KEY --confirm
chert-sdk facetime status | lines | workers
chert-sdk billing status (not available yet)
Expand All @@ -16,6 +18,13 @@ const help = `chert-sdk — unified Chert SDK CLI
chert-sdk adopt [path] [--write] [--install] [--json]
chert-sdk context [--offline]

onboard (macOS) reads your number from Messages, signs you in by sending a one-time
code from your Messages app, sets up the trial, starts the FaceTime demo call, then
finishes for this directory: wiring instructions for your coding agent in a project,
the personal agent CLI elsewhere. Without --numbers or --yes it asks, and with --json
or no terminal it stops with numbers_required. Output with --json is one JSON line
per step.

context prints SDK, credential, account and documentation context for coding
agents. It reads FaceTime lines, and identity with a CLI login (not a project API
key), when CHERT_FACETIME_TOKEN and CHERT_PROJECT_ID are set; --offline skips
Expand All @@ -34,8 +43,8 @@ Credentials by surface (service-specific, not interchangeable):
iMessage defaults to https://console.trychert.com; CHERT_BASE_URL optionally overrides it.
FaceTime defaults to https://facetime.trychert.com; CHERT_FACETIME_URL optionally
overrides the origin.
FaceTime commands inspect existing access/lines; no calls, media relay,
concierge onboarding or automatic shared-line allocation is implemented.
FaceTime commands inspect existing access/lines; they place no calls and relay no
media. Only onboard allocates shared access and requests the demo call.
The billing commands are not available yet: the backend refuses CLI credentials,
so today billing only works from a signed-in browser session. Once available,
buy charges the saved card and is refused while a subscription is live; when a
Expand All @@ -56,6 +65,7 @@ async function main() {
const [channel, command, ...args] = process.argv.slice(2);
if (channel === 'adopt') { process.exitCode = adopt(process.argv.slice(3)); return; }
if (!channel || process.argv.includes('--help') || process.argv.includes('-h')) { console.log(help); return; }
if (channel === 'onboard') { process.exitCode = await runOnboard(process.argv.slice(3)); return; }
// Reject unknown commands before asking for credentials, so typos are not reported as missing configuration.
const unknown = channel === 'context' ? (command !== undefined && command !== '--offline') || args.length > 0
: channel === 'imessage' ? command !== 'send'
Expand Down Expand Up @@ -138,7 +148,7 @@ async function context(env: NodeJS.ProcessEnv, offline: boolean) {
},
missing: ['CHERT_PROJECT_ID', 'CHERT_API_KEY', 'CHERT_FACETIME_TOKEN'].filter(name => !set(name)),
account: { facetime: offline ? 'skipped' : facetimeAccount ?? 'not_configured', imessage: 'not_inspected' },
commands: ['imessage send', 'facetime status', 'facetime lines', 'facetime workers', 'billing status (not available yet)', 'billing card (not available yet)', 'billing buy (not available yet)', 'billing cancel (not available yet)', 'adopt', 'context'],
commands: ['onboard', 'imessage send', 'facetime status', 'facetime lines', 'facetime workers', 'billing status (not available yet)', 'billing card (not available yet)', 'billing buy (not available yet)', 'billing cancel (not available yet)', 'adopt', 'context'],
docs: { setup: `${DOCS}/docs/SETUP.md`, llms: `${DOCS}/llms.txt`, ...(existsSync(llmsFile) ? { llmsFile } : {}), readme: `${DOCS}/README.md`,
compatibility: `${DOCS}/COMPATIBILITY.md` },
agentCli: { package: '@trychert/facetime', bin: 'chert', use: 'Browser login and putting a LiveKit voice or video agent on a FaceTime line' },
Expand Down
Loading
Loading