@coswic/aipay 0.6.0 → 0.7.0

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
@@ -14,7 +14,7 @@ npx @coswic/aipay test
14
14
  | Command | What it does |
15
15
  |---|---|
16
16
  | `aipay login` / `whoami` / `logout` | Sign in as the developer to manage your Apps; on a remote machine use `login --device`. This is separate from an end user authorizing AI spending in your App. |
17
- | `aipay region` / `region set --country <CC> [--state US-XX] --accept-terms` | Show or set your country/region (US: state) and accept the current Terms of Service. The developer API answers 403 `region_required` until both are on file. |
17
+ | `aipay region` / `region set --country <CC> [--state US-XX --line1 <street> --city <city> --zip <ZIP>] --accept-terms` | Show or set your country/region and accept the current Terms of Service; US accounts also need the full postal address (the backend locates the sales-tax jurisdiction: state / county / city / special district — `aipay region` shows it). The developer API answers 403 `region_required` until everything is on file. |
18
18
  | `aipay apps list` / `apps create --name <name>` | List or create Apps using your developer authorization. |
19
19
  | `aipay apps callbacks list --app <client_id>` | Read the registered callback URLs. In a linked project, `--app` can be omitted; `--dir` selects the project. |
20
20
  | `aipay apps callbacks add --app <client_id> --callback <url>` | Add a registered callback while preserving the others. Adding an existing URL makes no write. |
@@ -1,5 +1,8 @@
1
1
  /**
2
- * `aipay region` (show) and `aipay region set --country <CC> [--state US-XX] [--accept-terms]`.
2
+ * `aipay region` (show) and
3
+ * `aipay region set --country <CC> [--state US-XX --line1 <street> [--line2 <apt>] --city <city> --zip <ZIP>] [--accept-terms]`.
4
+ * US accounts (0121) must carry a full postal address: the backend locates the sales-tax jurisdiction
5
+ * (state / county / city / special district) from it; `aipay region` shows the lookup result.
3
6
  *
4
7
  * The developer API is closed (403 region_required) until the account has a declared region and an
5
8
  * accepted current Terms of Service version. Both endpoints used here are exempt from that gate:
@@ -9,6 +12,25 @@
9
12
  */
10
13
  import { type ParsedArgs } from '../args.js';
11
14
  import { type Reporter } from '../output.js';
15
+ export interface RegionAddress {
16
+ line1: string;
17
+ line2: string | null;
18
+ city: string;
19
+ postal_code: string;
20
+ }
21
+ export interface TaxLocationState {
22
+ status: 'pending' | 'resolved' | 'failed';
23
+ provider: string | null;
24
+ jurisdictions: Array<{
25
+ level: string;
26
+ name: string;
27
+ rate_bps: number;
28
+ }> | null;
29
+ combined_rate_bps: number | null;
30
+ standardized_postal_code: string | null;
31
+ county: string | null;
32
+ resolved_at: string | null;
33
+ }
12
34
  export interface RegionState {
13
35
  country: string | null;
14
36
  subdivision: string | null;
@@ -16,5 +38,9 @@ export interface RegionState {
16
38
  declared: boolean;
17
39
  terms_current: boolean;
18
40
  complete: boolean;
41
+ /** US only (0121): the full postal address on file */
42
+ address?: RegionAddress | null;
43
+ /** US only: background jurisdiction lookup (state / county / city / special district) */
44
+ tax_location?: TaxLocationState | null;
19
45
  }
20
46
  export declare function runRegion(args: ParsedArgs, out: Reporter, env?: NodeJS.ProcessEnv): Promise<number>;
@@ -1,5 +1,8 @@
1
1
  /**
2
- * `aipay region` (show) and `aipay region set --country <CC> [--state US-XX] [--accept-terms]`.
2
+ * `aipay region` (show) and
3
+ * `aipay region set --country <CC> [--state US-XX --line1 <street> [--line2 <apt>] --city <city> --zip <ZIP>] [--accept-terms]`.
4
+ * US accounts (0121) must carry a full postal address: the backend locates the sales-tax jurisdiction
5
+ * (state / county / city / special district) from it; `aipay region` shows the lookup result.
3
6
  *
4
7
  * The developer API is closed (403 region_required) until the account has a declared region and an
5
8
  * accepted current Terms of Service version. Both endpoints used here are exempt from that gate:
@@ -11,7 +14,7 @@ import { flagBool, flagString } from '../args.js';
11
14
  import { createAuthStore } from '../auth-store.js';
12
15
  import { errorMessage, openDeveloperApi } from '../developer-api.js';
13
16
  import { CliError, EXIT } from '../output.js';
14
- const FIX_HINT = 'aipay region set --country <ISO 3166-1> [--state US-XX] --accept-terms';
17
+ const FIX_HINT = 'aipay region set --country <ISO 3166-1> [--state US-XX --line1 <street> --city <city> --zip <ZIP>] --accept-terms';
15
18
  async function currentRegion(api) {
16
19
  const res = await api.request('GET', '/v1/developer/me/region');
17
20
  if (res.status !== 200 || !res.body?.region)
@@ -29,6 +32,15 @@ function report(out, apiBase, doc, saved = false) {
29
32
  out.line(r.complete ? 'ok' : 'warn', saved ? `region saved: ${place}` : r.complete ? `region ${place}` : 'region incomplete');
30
33
  out.kv('country', r.country ?? '—');
31
34
  out.kv('state', r.subdivision ?? '—');
35
+ if (r.country === 'US') {
36
+ const a = r.address;
37
+ out.kv('address', a
38
+ ? [a.line1, a.line2, a.city, `${r.subdivision?.slice(3) ?? ''} ${a.postal_code}`.trim()]
39
+ .filter(Boolean)
40
+ .join(', ')
41
+ : 'missing (required for the US)');
42
+ out.kv('tax', taxLocationText(r.tax_location ?? null));
43
+ }
32
44
  out.kv('terms', r.terms_current
33
45
  ? `accepted version ${r.terms_version}`
34
46
  : `${r.terms_version ? `accepted ${r.terms_version}, ` : ''}version ${doc.terms_version_required} needs acceptance (${termsUrl(apiBase, doc)})`);
@@ -41,6 +53,40 @@ function report(out, apiBase, doc, saved = false) {
41
53
  ...(saved ? { saved: true } : {}),
42
54
  });
43
55
  }
56
+ /** Background jurisdiction lookup, one line: pending / failed / combined rate with the per-level breakdown. */
57
+ function taxLocationText(tl) {
58
+ if (!tl || tl.status === 'pending')
59
+ return 'jurisdiction lookup pending (background)';
60
+ if (tl.status === 'failed')
61
+ return 'address could not be located — check it and set it again';
62
+ const pct = (bps) => `${(bps / 100).toFixed(2).replace(/\.?0+$/, '')}%`;
63
+ const parts = (tl.jurisdictions ?? [])
64
+ .filter((j) => j.rate_bps > 0)
65
+ .map((j) => `${j.level} ${j.name} ${pct(j.rate_bps)}`);
66
+ return `combined ${pct(tl.combined_rate_bps ?? 0)} (${parts.join('; ') || '—'})${tl.standardized_postal_code ? ` · ZIP+4 ${tl.standardized_postal_code}` : ''}`;
67
+ }
68
+ /** US address flags → body.address; usage errors before any network call. */
69
+ function parseAddressFlags(flags) {
70
+ const squash = (v) => (v ?? '').trim().replace(/\s+/g, ' ');
71
+ const line1 = squash(flagString(flags, 'line1'));
72
+ const line2 = squash(flagString(flags, 'line2'));
73
+ const city = squash(flagString(flags, 'city'));
74
+ const zipRaw = squash(flagString(flags, 'zip') ?? flagString(flags, 'postal-code')).replace(/[\s-]/g, '');
75
+ if (!line1 || line1.length > 120)
76
+ throw new CliError('usage', 'the United States needs --line1 <street address> (1–120 characters)', EXIT.usage);
77
+ if (line2.length > 120)
78
+ throw new CliError('usage', '--line2 is limited to 120 characters', EXIT.usage);
79
+ if (!city || city.length > 80)
80
+ throw new CliError('usage', 'the United States needs --city <city> (1–80 characters)', EXIT.usage);
81
+ if (!/^\d{5}(\d{4})?$/.test(zipRaw))
82
+ throw new CliError('usage', 'the United States needs --zip <5 digits or ZIP+4> (e.g. 94105 or 94105-1234)', EXIT.usage);
83
+ return {
84
+ line1,
85
+ line2: line2 || null,
86
+ city,
87
+ postal_code: zipRaw.length === 9 ? `${zipRaw.slice(0, 5)}-${zipRaw.slice(5)}` : zipRaw,
88
+ };
89
+ }
44
90
  export async function runRegion(args, out, env = process.env) {
45
91
  const sub = args.positionals[0] ?? 'show';
46
92
  if (sub !== 'show' && sub !== 'set')
@@ -52,6 +98,7 @@ export async function runRegion(args, out, env = process.env) {
52
98
  const acceptTerms = flagBool(args.flags, 'accept-terms');
53
99
  let country = null;
54
100
  let state = null;
101
+ let address = null;
55
102
  if (sub === 'set') {
56
103
  if (!countryRaw)
57
104
  throw new CliError('usage', 'missing --country <ISO 3166-1 alpha-2 code, e.g. TW or US>', EXIT.usage);
@@ -69,6 +116,13 @@ export async function runRegion(args, out, env = process.env) {
69
116
  }
70
117
  if (country === 'US' && !state)
71
118
  throw new CliError('usage', 'the United States needs --state (e.g. --state US-CA)', EXIT.usage);
119
+ // 0121: the US needs a full address (street, city, ZIP) — the backend locates the sales-tax jurisdiction from it
120
+ if (country === 'US')
121
+ address = parseAddressFlags(args.flags);
122
+ else if (flagString(args.flags, 'line1') ||
123
+ flagString(args.flags, 'city') ||
124
+ flagString(args.flags, 'zip'))
125
+ throw new CliError('usage', '--line1/--line2/--city/--zip are only accepted for --country US', EXIT.usage);
72
126
  }
73
127
  else if (countryRaw || stateRaw || acceptTerms) {
74
128
  throw new CliError('usage', '`aipay region` shows the current state; use `aipay region set …` to change it', EXIT.usage);
@@ -84,6 +138,7 @@ export async function runRegion(args, out, env = process.env) {
84
138
  const body = {
85
139
  country,
86
140
  ...(state ? { subdivision: state } : {}),
141
+ ...(address ? { address: { ...address, line2: address.line2 ?? undefined } } : {}),
87
142
  ...(acceptTerms ? { accept_terms: true, terms_version: current.terms_version_required } : {}),
88
143
  };
89
144
  const res = await api.request('POST', '/v1/developer/me/region', { body });
package/dist/main.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const HELP = "aipay \u2014 AI-Pay CLI\n\nUsage\n aipay login [--api <base>] [--device] [--json] developer sign-in for the CLI itself\n aipay whoami [--json] \u00B7 aipay logout [--json]\n aipay region [--json] your country/region + Terms of Service status\n aipay region set --country <CC> [--state US-XX] [--accept-terms] [--json]\n aipay apps list [--json]\n aipay apps create --name <name> [--callback <url>] [--json]\n aipay apps callbacks list [--app <client_id>] [--dir <path>] [--json]\n aipay apps callbacks add|remove --callback <url> [--app <client_id>] [--dir <path>] [--json]\n aipay init --app <client_id> [--auth <origin>] [--api <base>] [--callback <url>] [--name <app name>]\n [--framework next|express|fastify|hono|vite|node] override detection\n [--dir <path>] [--dry-run] [--no-install] [--json]\n aipay init --create <app name> [--api <base>] ... create the app (needs login) and link it\n aipay test [--model <id>] [--callback <registered-loopback-url>] [--port <n>] [--no-open] [--timeout <seconds>] [--dir <path>] [--json]\n aipay doctor [--offline] [--dir <path>] [--json]\n aipay status [--dir <path>] [--json]\n aipay unlink [--dry-run] [--dir <path>] [--json]\n\nlogin sign in as a developer (loopback PKCE, audience aipay-developer-api); tokens go to the\n OS keychain or a 0600 file \u2014 never into the project\nregion the developer API stays closed (403 region_required) until your account has a country/region\n (US: a state) and has accepted the current Terms of Service \u2014 \"region set\" does both; the\n version to accept and the terms URL are printed by \"aipay region\"\napps list/create your apps; callbacks list/add/remove manages registered redirects\n callback changes preserve other URLs and reject concurrent edits\ninit link this project to an app: .aipay/project.json, .env.local, .gitignore,\n a callback route for your framework, aipay-agent.md, and the SDK install\n (--auth/--api are optional once you are signed in; --create makes the app first)\ntest real sign-in via a loopback callback (any free port on 127.0.0.1) + one chat completion \u2192 receipt\ndoctor check the link, files, SDK, and that AI-Pay is reachable\nstatus what this directory is linked to\nunlink forget the link (keeps your code)\n\nExit codes: 0 ok \u00B7 1 problem you can fix \u00B7 2 usage \u00B7 3 network/auth\n";
1
+ export declare const HELP = "aipay \u2014 AI-Pay CLI\n\nUsage\n aipay login [--api <base>] [--device] [--json] developer sign-in for the CLI itself\n aipay whoami [--json] \u00B7 aipay logout [--json]\n aipay region [--json] your country/region + Terms of Service status\n aipay region set --country <CC> [--state US-XX --line1 <street> [--line2 <apt>] --city <city> --zip <ZIP>]\n [--accept-terms] [--json] US accounts need the full address (sales-tax jurisdiction)\n aipay apps list [--json]\n aipay apps create --name <name> [--callback <url>] [--json]\n aipay apps callbacks list [--app <client_id>] [--dir <path>] [--json]\n aipay apps callbacks add|remove --callback <url> [--app <client_id>] [--dir <path>] [--json]\n aipay init --app <client_id> [--auth <origin>] [--api <base>] [--callback <url>] [--name <app name>]\n [--framework next|express|fastify|hono|vite|node] override detection\n [--dir <path>] [--dry-run] [--no-install] [--json]\n aipay init --create <app name> [--api <base>] ... create the app (needs login) and link it\n aipay test [--model <id>] [--callback <registered-loopback-url>] [--port <n>] [--no-open] [--timeout <seconds>] [--dir <path>] [--json]\n aipay doctor [--offline] [--dir <path>] [--json]\n aipay status [--dir <path>] [--json]\n aipay unlink [--dry-run] [--dir <path>] [--json]\n\nlogin sign in as a developer (loopback PKCE, audience aipay-developer-api); tokens go to the\n OS keychain or a 0600 file \u2014 never into the project\nregion the developer API stays closed (403 region_required) until your account has a country/region\n (US: a state) and has accepted the current Terms of Service \u2014 \"region set\" does both; the\n version to accept and the terms URL are printed by \"aipay region\"\napps list/create your apps; callbacks list/add/remove manages registered redirects\n callback changes preserve other URLs and reject concurrent edits\ninit link this project to an app: .aipay/project.json, .env.local, .gitignore,\n a callback route for your framework, aipay-agent.md, and the SDK install\n (--auth/--api are optional once you are signed in; --create makes the app first)\ntest real sign-in via a loopback callback (any free port on 127.0.0.1) + one chat completion \u2192 receipt\ndoctor check the link, files, SDK, and that AI-Pay is reachable\nstatus what this directory is linked to\nunlink forget the link (keeps your code)\n\nExit codes: 0 ok \u00B7 1 problem you can fix \u00B7 2 usage \u00B7 3 network/auth\n";
2
2
  export declare function main(argv: string[], io?: {
3
3
  stdout?: (s: string) => void;
4
4
  stderr?: (s: string) => void;
package/dist/main.js CHANGED
@@ -16,7 +16,8 @@ Usage
16
16
  aipay login [--api <base>] [--device] [--json] developer sign-in for the CLI itself
17
17
  aipay whoami [--json] · aipay logout [--json]
18
18
  aipay region [--json] your country/region + Terms of Service status
19
- aipay region set --country <CC> [--state US-XX] [--accept-terms] [--json]
19
+ aipay region set --country <CC> [--state US-XX --line1 <street> [--line2 <apt>] --city <city> --zip <ZIP>]
20
+ [--accept-terms] [--json] US accounts need the full address (sales-tax jurisdiction)
20
21
  aipay apps list [--json]
21
22
  aipay apps create --name <name> [--callback <url>] [--json]
22
23
  aipay apps callbacks list [--app <client_id>] [--dir <path>] [--json]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coswic/aipay",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "AI-Pay CLI — developer sign-in, App and callback management, project integration, diagnostics, and real sign-in + AI receipt verification.",
5
5
  "license": "MIT",
6
6
  "type": "module",