Skip to content
Merged
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
6 changes: 3 additions & 3 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ CLI-native affiliate tracking. Connect Stripe, track commissions, manage partner
# Create your affiliate program
npx affitor init

# Connect Stripe in the Affitor dashboard: program Settings, Connect Stripe
# Connect Stripe in the Affitor dashboard: Program settings → Payments, Connect Stripe

# Check program health
npx affitor status
Expand All @@ -25,7 +25,7 @@ npx affitor test sale
|---|---|
| `affitor init` | Create a new affiliate program. Interactive prompts or `--no-interactive` with flags. |
| `affitor onboard` | Wire Affitor into an existing app end-to-end: detect → install browser tracking → inject the Stripe sale call → verify. |
| `affitor setup stripe` | Points you to the dashboard, where Stripe is connected (program Settings, Connect Stripe). Exits 1. |
| `affitor setup stripe` | Points you to the dashboard, where Stripe is connected (Program settings → Payments, Connect Stripe). Exits 1. |
| `affitor setup dns` | Set up DNS CNAME tracking (coming soon). |
| `affitor status` | Show program health: DNS, Stripe connection, recent events. |
| `affitor test [type]` | Send a test event (`click`, `lead`, or `sale`). |
Expand Down Expand Up @@ -93,7 +93,7 @@ Running `npx affitor init` creates:

## Stripe

Stripe is connected from the Affitor dashboard (program Settings, Connect Stripe), not from the CLI. Affitor's own webhook then records sales and renewals; add Affitor metadata to your checkout so sales are attributed reliably.
Stripe is connected from the Affitor dashboard (Program settings → Payments, Connect Stripe), not from the CLI. Affitor's own webhook then records sales and renewals; add Affitor metadata to your checkout so sales are attributed reliably.

## Requirements

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export async function runInit(
"Affitor already configured in this directory.\n" +
" Use `npx affitor status` to check your program.\n" +
" `init` creates a new program: it does not restore this one and does not replace its API key.\n" +
" For a new key, ask a workspace owner of this program to open Affitor → Settings → API Key " +
" For a new key, ask a workspace owner of this program to open Affitor → Program settings → Developers → API keys " +
"and click Regenerate, then replace the key this project uses.",
);
if (flags.json) {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/setup-stripe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { CLIFlags } from "../types.js";
* 410 Gone (W37-1690), and the webhook it created pointed at a URL the CMS does not serve.
*/
export const STRIPE_SETUP_MOVED =
"Stripe is connected from the Affitor dashboard, not the CLI: open the program, go to Settings, and use Connect Stripe.";
"Stripe is connected from the Affitor dashboard, not the CLI: open Affitor → Program settings → Payments and click Connect Stripe.";

export function registerSetupCommand(program: Command) {
const setup = program
Expand Down Expand Up @@ -80,6 +80,6 @@ function runSetupDns(flags: CLIFlags) {
"",
]);

logger.info(` Get started: connect Stripe from the Affitor dashboard (program Settings, Connect Stripe).`);
logger.info(` Get started: connect Stripe from the Affitor dashboard (Program settings → Payments, Connect Stripe).`);
logger.newline();
}
4 changes: 2 additions & 2 deletions packages/cli/src/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export async function runStatus(flags: CLIFlags) {
logger.step(`DNS: Add CNAME t.${domain} → track.affitor.com`);
}
if (!status.stripe_connected) {
logger.step("Stripe: Connect it in the Affitor dashboard (program Settings, Connect Stripe)");
logger.step("Stripe: Connect it in the Affitor dashboard (Program settings → Payments, Connect Stripe)");
}
} catch (err) {
if (err instanceof APIError) {
Expand All @@ -102,7 +102,7 @@ export async function runStatus(flags: CLIFlags) {
// only AFFITOR_API_KEY would leave the refused key on the wire.
logger.error(
"API key expired or invalid.\n" +
` Ask a workspace owner of ${config ? `program ${config.program_id}` : "the program"} to open Affitor → Settings → API Key and click Regenerate.\n` +
` Ask a workspace owner of ${config ? `program ${config.program_id}` : "the program"} to open Affitor → Program settings → Developers → API keys and click Regenerate.\n` +
` Then replace the key in ${apiKeySourceLabel(flags)} with the new one.`,
);
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export function writeEnvExample(

AFFITOR_API_KEY=${apiKey}
AFFITOR_PROGRAM_ID=${config.program_id}
# STRIPE_CONNECTED_ACCOUNT_ID= (connect Stripe in the Affitor dashboard: program Settings, Connect Stripe)
# STRIPE_CONNECTED_ACCOUNT_ID= (connect Stripe in the Affitor dashboard: Program settings → Payments, Connect Stripe)
`;
writeFileSync(join(dir, ".env.example"), content);
}
Expand Down Expand Up @@ -367,7 +367,7 @@ curl -X POST ${config.api_url}/api/v1/track/lead \\

### 3. Sale/Revenue Tracking

**Option A: Stripe (recommended)**: connect Stripe in the Affitor dashboard (program Settings, Connect Stripe). Affitor's webhook records sales; add Affitor metadata to your checkout so sales are attributed reliably.
**Option A: Stripe (recommended)**: connect Stripe in the Affitor dashboard (Program settings → Payments, Connect Stripe). Affitor's webhook records sales; add Affitor metadata to your checkout so sales are attributed reliably.

**Option B: Sale API (any payment provider)**

Expand Down
6 changes: 3 additions & 3 deletions packages/cli/tests/api-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe("AffitorAPI error parsing", () => {
const ROTATION_WARNING = {
code: "api_key_rotation_required",
message: "This API key must be replaced by 2026-10-03.",
hint: "Create a new key in Affitor → Settings → API Key, then update AFFITOR_API_KEY on your server.",
hint: "In Affitor → Program settings → Developers → API keys, click Regenerate to get a new key, then update AFFITOR_API_KEY on your server.",
rotate_by: "2026-10-03T00:00:00Z",
docs_url: "https://affitor.com/docs/api-reference/errors#api_key_rotation_required",
};
Expand All @@ -80,7 +80,7 @@ const ROTATION_ERROR = {
error: {
code: "api_key_rotation_required",
message: "This API key was retired on 2026-10-03.",
hint: "Create a new key in Affitor → Settings → API Key.",
hint: "In Affitor → Program settings → Developers → API keys, click Regenerate to get a new key.",
docs_url: "https://affitor.com/docs/api-reference/errors#api_key_rotation_required",
},
};
Expand Down Expand Up @@ -225,7 +225,7 @@ describe("AffitorAPI key-rotation signal", () => {
} catch (e) {
expect(e).toBeInstanceOf(APIError);
expect((e as APIError).message).toContain(ROTATION_ERROR.error.message);
expect((e as APIError).message).toContain("Create a new key in Affitor");
expect((e as APIError).message).toContain("click Regenerate to get a new key");
}
});
});
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/tests/exit-codes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const ROTATION_401 = {
error: {
code: "api_key_rotation_required",
message: "This API key was retired on 2026-10-03.",
hint: "Create a new key in Affitor → Settings → API Key.",
hint: "In Affitor → Program settings → Developers → API keys, click Regenerate to get a new key.",
},
};

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/tests/init-already-configured.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { CLIFlags } from "../src/types";
* Product rule: `init` creates a new program. It does not restore the program this directory
* already points at, and it does not replace that program's API key. The message has to say
* both, and name what does produce a new key: a workspace owner of the program regenerating
* it in Settings → API Key.
* it in Program settings → Developers → API keys.
*/

const flags: CLIFlags = {
Expand Down Expand Up @@ -75,7 +75,7 @@ describe("affitor init in a directory that is already configured", () => {
expect(out).toContain("does not restore this one");
expect(out).toContain("does not replace its API key");
expect(out).toContain("workspace owner of this program");
expect(out).toContain("Affitor → Settings → API Key");
expect(out).toContain("Affitor → Program settings → Developers → API keys");
expect(out).toContain("Regenerate");
expect(out).not.toContain("to reinitialize");
});
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/tests/onboard-rotation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const ROTATION_401 = {
error: {
code: "api_key_rotation_required",
message: "This API key was retired on 2026-10-03.",
hint: "Create a new key in Affitor → Settings → API Key.",
hint: "In Affitor → Program settings → Developers → API keys, click Regenerate to get a new key.",
},
};

Expand Down
4 changes: 4 additions & 0 deletions packages/cli/tests/setup-stripe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ describe("affitor setup stripe", () => {
expect(fetch).not.toHaveBeenCalled();
});

it("names the dashboard page as it is today (nav v2)", () => {
expect(STRIPE_SETUP_MOVED).toContain("Affitor → Program settings → Payments and click Connect Stripe");
});

it("old flags are accepted and still get the directions", async () => {
await run(["setup", "stripe", "--stripe-secret-key", "sk_test_x", "--stripe-client-id", "ca_x"]);
expect(process.exitCode).toBe(1);
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/tests/status-invalid-key.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ describe("affitor status: what a refused key is told to do", () => {
const out = await statusStderr({ ...baseFlags, apiKey: "aff_test_key" });

expect(out).toContain(`workspace owner of program ${PROGRAM_ID}`);
expect(out).toContain("Affitor → Settings → API Key");
expect(out).toContain("Affitor → Program settings → Developers → API keys");
expect(out).toContain("Regenerate");
expect(out).not.toContain("affitor init");
});
Expand Down Expand Up @@ -139,7 +139,7 @@ describe("affitor status: what a refused key is told to do", () => {
// the API said. Written out here rather than imported: this is the CMS 401 text.
const fromApi =
"Invalid API key. To get a working key for the program you already have, ask a workspace " +
"owner of that program to open Affitor → Settings → API Key and click Regenerate, then " +
"owner of that program to open Affitor → Program settings → Developers → API keys and click Regenerate, then " +
"replace the key you are sending with the new one.";
respond401({ data: null, error: { status: 401, message: fromApi } });

Expand Down Expand Up @@ -202,14 +202,14 @@ describe("affitor status: what a refused key is told to do", () => {
error: {
code: "api_key_rotation_required",
message: "This API key was retired on 2026-10-03.",
hint: "Create a new key in Affitor → Settings → API Key.",
hint: "In Affitor → Program settings → Developers → API keys, click Regenerate to get a new key.",
},
});

const out = await statusStderr({ ...baseFlags, apiKey: "aff_test_key" });

expect(out).toContain("This API key was retired on 2026-10-03.");
expect(out).toContain("Create a new key in Affitor → Settings → API Key.");
expect(out).toContain("In Affitor → Program settings → Developers → API keys, click Regenerate to get a new key.");
expect(out).not.toContain("affitor init");
});
});
Loading