@coswic/aipay 0.5.0 → 0.6.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Coswic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -14,6 +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
18
  | `aipay apps list` / `apps create --name <name>` | List or create Apps using your developer authorization. |
18
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. |
19
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. |
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `aipay region` (show) and `aipay region set --country <CC> [--state US-XX] [--accept-terms]`.
3
+ *
4
+ * The developer API is closed (403 region_required) until the account has a declared region and an
5
+ * accepted current Terms of Service version. Both endpoints used here are exempt from that gate:
6
+ * GET /v1/developer/me/region → current state + the version to accept
7
+ * POST /v1/developer/me/region → declare (same body as the dashboard)
8
+ * Non-interactive on purpose (agents drive this): everything is a flag; missing input is a usage error.
9
+ */
10
+ import { type ParsedArgs } from '../args.js';
11
+ import { type Reporter } from '../output.js';
12
+ export interface RegionState {
13
+ country: string | null;
14
+ subdivision: string | null;
15
+ terms_version: string | null;
16
+ declared: boolean;
17
+ terms_current: boolean;
18
+ complete: boolean;
19
+ }
20
+ export declare function runRegion(args: ParsedArgs, out: Reporter, env?: NodeJS.ProcessEnv): Promise<number>;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * `aipay region` (show) and `aipay region set --country <CC> [--state US-XX] [--accept-terms]`.
3
+ *
4
+ * The developer API is closed (403 region_required) until the account has a declared region and an
5
+ * accepted current Terms of Service version. Both endpoints used here are exempt from that gate:
6
+ * GET /v1/developer/me/region → current state + the version to accept
7
+ * POST /v1/developer/me/region → declare (same body as the dashboard)
8
+ * Non-interactive on purpose (agents drive this): everything is a flag; missing input is a usage error.
9
+ */
10
+ import { flagBool, flagString } from '../args.js';
11
+ import { createAuthStore } from '../auth-store.js';
12
+ import { errorMessage, openDeveloperApi } from '../developer-api.js';
13
+ import { CliError, EXIT } from '../output.js';
14
+ const FIX_HINT = 'aipay region set --country <ISO 3166-1> [--state US-XX] --accept-terms';
15
+ async function currentRegion(api) {
16
+ const res = await api.request('GET', '/v1/developer/me/region');
17
+ if (res.status !== 200 || !res.body?.region)
18
+ throw new CliError('remote', `GET /v1/developer/me/region → ${errorMessage(res.body?.error ?? res.body, res.status)}`, EXIT.remote);
19
+ return res.body;
20
+ }
21
+ function termsUrl(apiBase, doc) {
22
+ return new URL(doc.terms_path ?? '/terms', apiBase).toString();
23
+ }
24
+ function report(out, apiBase, doc, saved = false) {
25
+ const r = doc.region;
26
+ const place = r.country
27
+ ? `${r.country}${r.subdivision ? ` ${r.subdivision}` : ''}`
28
+ : 'not declared';
29
+ out.line(r.complete ? 'ok' : 'warn', saved ? `region saved: ${place}` : r.complete ? `region ${place}` : 'region incomplete');
30
+ out.kv('country', r.country ?? '—');
31
+ out.kv('state', r.subdivision ?? '—');
32
+ out.kv('terms', r.terms_current
33
+ ? `accepted version ${r.terms_version}`
34
+ : `${r.terms_version ? `accepted ${r.terms_version}, ` : ''}version ${doc.terms_version_required} needs acceptance (${termsUrl(apiBase, doc)})`);
35
+ if (!r.complete)
36
+ out.line('info', `run \`${FIX_HINT}\``);
37
+ out.result({
38
+ ...r,
39
+ terms_version_required: doc.terms_version_required,
40
+ terms_url: termsUrl(apiBase, doc),
41
+ ...(saved ? { saved: true } : {}),
42
+ });
43
+ }
44
+ export async function runRegion(args, out, env = process.env) {
45
+ const sub = args.positionals[0] ?? 'show';
46
+ if (sub !== 'show' && sub !== 'set')
47
+ throw new CliError('usage', `unknown region subcommand "${sub}" (show | set)`, EXIT.usage);
48
+ if (args.positionals.length > 1)
49
+ throw new CliError('usage', 'unexpected region positional argument', EXIT.usage);
50
+ const countryRaw = flagString(args.flags, 'country');
51
+ const stateRaw = flagString(args.flags, 'state');
52
+ const acceptTerms = flagBool(args.flags, 'accept-terms');
53
+ let country = null;
54
+ let state = null;
55
+ if (sub === 'set') {
56
+ if (!countryRaw)
57
+ throw new CliError('usage', 'missing --country <ISO 3166-1 alpha-2 code, e.g. TW or US>', EXIT.usage);
58
+ country = countryRaw.trim().toUpperCase();
59
+ if (!/^[A-Z]{2}$/.test(country))
60
+ throw new CliError('usage', `--country must be a two-letter ISO 3166-1 code (got "${countryRaw}")`, EXIT.usage);
61
+ if (stateRaw) {
62
+ state = stateRaw.trim().toUpperCase();
63
+ if (!/^[A-Z]{2}-[A-Z0-9]{1,3}$/.test(state))
64
+ throw new CliError('usage', `--state must look like US-CA (got "${stateRaw}")`, EXIT.usage);
65
+ if (!state.startsWith(`${country}-`))
66
+ throw new CliError('usage', `--state ${state} does not belong to --country ${country}`, EXIT.usage);
67
+ if (country !== 'US')
68
+ throw new CliError('usage', '--state is only accepted for --country US', EXIT.usage);
69
+ }
70
+ if (country === 'US' && !state)
71
+ throw new CliError('usage', 'the United States needs --state (e.g. --state US-CA)', EXIT.usage);
72
+ }
73
+ else if (countryRaw || stateRaw || acceptTerms) {
74
+ throw new CliError('usage', '`aipay region` shows the current state; use `aipay region set …` to change it', EXIT.usage);
75
+ }
76
+ const api = await openDeveloperApi(createAuthStore(env));
77
+ const current = await currentRegion(api);
78
+ if (sub === 'show') {
79
+ report(out, api.auth.api_base, current);
80
+ return EXIT.ok;
81
+ }
82
+ if (!current.region.terms_current && !acceptTerms)
83
+ throw new CliError('terms_required', `the AI-Pay Terms of Service (version ${current.terms_version_required}, ${termsUrl(api.auth.api_base, current)}) need your explicit acceptance — read them, then rerun with --accept-terms`, EXIT.problem);
84
+ const body = {
85
+ country,
86
+ ...(state ? { subdivision: state } : {}),
87
+ ...(acceptTerms ? { accept_terms: true, terms_version: current.terms_version_required } : {}),
88
+ };
89
+ const res = await api.request('POST', '/v1/developer/me/region', { body });
90
+ if (res.status === 409)
91
+ throw new CliError('terms_version_changed', 'the Terms of Service changed while you were reading — run `aipay region` to see the new version, then accept again', EXIT.problem);
92
+ if (res.status !== 200 || !res.body?.region)
93
+ throw new CliError('region_rejected', `could not save the region: ${errorMessage(res.body?.error ?? res.body, res.status)}`, res.status === 400 ? EXIT.usage : EXIT.problem);
94
+ report(out, api.auth.api_base, {
95
+ region: res.body.region,
96
+ terms_version_required: current.terms_version_required,
97
+ ...(current.terms_path ? { terms_path: current.terms_path } : {}),
98
+ }, true);
99
+ return EXIT.ok;
100
+ }
@@ -43,6 +43,10 @@ export async function openDeveloperApi(store) {
43
43
  catch {
44
44
  body = { raw: text.slice(0, 500) };
45
45
  }
46
+ // 0074 hard gate: the developer API is closed until region + current Terms are on file.
47
+ // `aipay region …` talks to the exempt endpoints, so this never traps the fix itself.
48
+ if (res.status === 403 && errorCodeOf(body) === 'region_required')
49
+ throw new CliError('region_required', 'AI-Pay needs your country/region and your acceptance of the current Terms of Service before the developer API can be used — run `aipay region set --country <ISO 3166-1> [--state US-XX] --accept-terms` (`aipay region` shows the version and terms URL)', EXIT.problem);
46
50
  return { status: res.status, body: body };
47
51
  },
48
52
  };
@@ -72,3 +76,9 @@ export function errorMessage(body, status) {
72
76
  const msg = typeof b.message === 'string' ? b.message : typeof b.error === 'string' ? b.error : '';
73
77
  return msg ? `${msg} (HTTP ${status})` : `HTTP ${status}`;
74
78
  }
79
+ function errorCodeOf(body) {
80
+ const err = body?.error;
81
+ if (err && typeof err === 'object' && typeof err.code === 'string')
82
+ return err.code;
83
+ return null;
84
+ }
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 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\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] [--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";
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
@@ -3,6 +3,7 @@ import { runApps } from './commands/apps.js';
3
3
  import { runDoctor } from './commands/doctor.js';
4
4
  import { runInit } from './commands/init.js';
5
5
  import { runLogin } from './commands/login.js';
6
+ import { runRegion } from './commands/region.js';
6
7
  import { runLogout, runWhoami } from './commands/session.js';
7
8
  import { runStatus } from './commands/status.js';
8
9
  import { runTest } from './commands/test.js';
@@ -14,6 +15,8 @@ export const HELP = `aipay — AI-Pay CLI
14
15
  Usage
15
16
  aipay login [--api <base>] [--device] [--json] developer sign-in for the CLI itself
16
17
  aipay whoami [--json] · aipay logout [--json]
18
+ aipay region [--json] your country/region + Terms of Service status
19
+ aipay region set --country <CC> [--state US-XX] [--accept-terms] [--json]
17
20
  aipay apps list [--json]
18
21
  aipay apps create --name <name> [--callback <url>] [--json]
19
22
  aipay apps callbacks list [--app <client_id>] [--dir <path>] [--json]
@@ -29,6 +32,9 @@ Usage
29
32
 
30
33
  login sign in as a developer (loopback PKCE, audience aipay-developer-api); tokens go to the
31
34
  OS keychain or a 0600 file — never into the project
35
+ region the developer API stays closed (403 region_required) until your account has a country/region
36
+ (US: a state) and has accepted the current Terms of Service — "region set" does both; the
37
+ version to accept and the terms URL are printed by "aipay region"
32
38
  apps list/create your apps; callbacks list/add/remove manages registered redirects
33
39
  callback changes preserve other URLs and reject concurrent edits
34
40
  init link this project to an app: .aipay/project.json, .env.local, .gitignore,
@@ -63,6 +69,8 @@ export async function main(argv, io) {
63
69
  return await runLogout(args, out);
64
70
  case 'whoami':
65
71
  return await runWhoami(args, out);
72
+ case 'region':
73
+ return await runRegion(args, out);
66
74
  case 'apps':
67
75
  return await runApps(args, out);
68
76
  case 'init':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coswic/aipay",
3
- "version": "0.5.0",
3
+ "version": "0.6.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",