@atmorouter/helper 0.2.2 → 0.2.3

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
@@ -2,17 +2,17 @@
2
2
 
3
3
  Configure coding agents for AtmoRouter, get IDE setup instructions, discover model IDs, and check a key without running inference. Changes are previewed first; automatic configuration merges existing settings and creates backups.
4
4
 
5
- Requires Node.js 22 or newer. The public npm package uses the `yaml` runtime dependency to safely parse and preserve YAML configuration. No npm account or repository access is required.
5
+ Requires Node.js 22 or newer. The package uses the `yaml` runtime dependency to safely parse and preserve YAML configuration. No repository access is required. Version 0.2.3 adds post-setup budget, routing-wait, and retry guidance while keeping generated configuration and shell output clean. The commands below pin that version; the versioned website archive is an alternative installation channel.
6
6
 
7
7
  ```sh
8
- npx @atmorouter/helper@0.2.2 --help
9
- npx @atmorouter/helper@0.2.2 targets
10
- npx @atmorouter/helper@0.2.2 models --search claude
11
- npx @atmorouter/helper@0.2.2 configure --target opencode --model YOUR_MODEL_ID
12
- npx @atmorouter/helper@0.2.2 configure --target claude --model YOUR_MODEL_ID --write
8
+ npx @atmorouter/helper@0.2.3 --help
9
+ npx @atmorouter/helper@0.2.3 targets
10
+ npx @atmorouter/helper@0.2.3 models --search claude
11
+ npx @atmorouter/helper@0.2.3 configure --target opencode --model YOUR_MODEL_ID
12
+ npx @atmorouter/helper@0.2.3 configure --target claude --model YOUR_MODEL_ID --write
13
13
  ```
14
14
 
15
- The same version is available as `https://atmorouter.dev/downloads/atmorouter-helper-0.2.2.tgz`, with a `.sha256` checksum alongside it. Run it with `npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.2.tgz --help`.
15
+ The same version is available as `https://atmorouter.dev/downloads/atmorouter-helper-0.2.3.tgz`, with a `.sha256` checksum alongside it. Run it with `npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.3.tgz --help`.
16
16
 
17
17
  With no command, the interactive wizard searches live text models and previews the selected setup. `configure` is preview-only unless `--write` is supplied. Non-interactive file writes additionally require `--yes`. Tests use temporary `--config-dir` folders, never real agent settings.
18
18
 
@@ -36,13 +36,27 @@ Guided targets print the steps to perform in your installed client; they do not
36
36
 
37
37
  Use `--shell powershell` for PowerShell snippets. `--config-dir PATH` explicitly selects a settings folder. `--offline` skips live model validation; it does not establish model availability or client compatibility. Preview output shows intended changes, not existing file contents.
38
38
 
39
+ ## Budgets after setup
40
+
41
+ After an automatic setup, preview, manual IDE integration, or bridge launch, open [Budgets](https://atmorouter.dev/dashboard/budgets). Select the actual API key used by that client, then choose an all-model percentage rule for broad coverage. Illustrative examples only: 20% below official rates or 50% below official rates. Review the displayed rates and choose your own limit. A family or exact-model rule covers only that target. Account and key rules both apply; the strictest input and output limits win. The helper never saves or raises budget rules automatically.
42
+
43
+ Market prices and tiers vary. A “From” price is a snapshot of the cheapest advertised offer, not a guaranteed execution price. Setup and key checks do not prove current availability within your limits. The CLI sends these reminders to stderr so piping an environment snippet from stdout still produces valid shell commands. Generated configuration files contain client settings only.
44
+
45
+ ## Waiting and retries in your harness
46
+
47
+ Routing can wait up to 30 seconds for capacity within your caps. This wait is fixed; generation time is additional. Set the client timeout to allow both routing and the expected generation duration. The local bridge defaults to a 120-second total timeout, including routing and generation; a caller using the bridge must allow for that total duration.
48
+
49
+ For HTTP 429 or temporary availability failures, honor `Retry-After` when present. Otherwise use bounded exponential backoff with jitter. Avoid blind retries and duplicate in-flight requests. After a timeout or interrupted stream, review the client outcome and usage before deciding to resend; a lost response does not establish that the request was unprocessed. The bridge does not automatically retry inference or raise price caps.
50
+
51
+ Correct invalid requests before retrying. For authentication errors, verify the key and account eligibility. For insufficient balance, review Billing. For `balance_temporarily_reserved`, allow existing requests to finish. For `price_limit_unavailable`, wait for capacity within your caps or deliberately review your budget. Adding funds does not resolve pricing availability.
52
+
39
53
  ## Credentials
40
54
 
41
55
  The helper never saves or prints your API key. Set `ATMOROUTER_API_KEY` privately through your shell or secret manager. OpenCode reads `{env:ATMOROUTER_API_KEY}`; Oh My Pi reads the environment variable named by `apiKey`. Continue uses `${{ secrets.ATMOROUTER_API_KEY }}`; for the IDE, manually add the secret to `~/.continue/.env` without committing that file. For Claude Code, set `ANTHROPIC_AUTH_TOKEN` to the same key before launching Claude. Guided IDE targets require you to enter the key in the client's own credential UI.
42
56
 
43
57
  ```sh
44
58
  export ANTHROPIC_AUTH_TOKEN="$ATMOROUTER_API_KEY"
45
- npx @atmorouter/helper@0.2.2 check-key
59
+ npx @atmorouter/helper@0.2.3 check-key
46
60
  ```
47
61
 
48
62
  `GET /v1/models` is public and **cannot validate a key**. The optional explicit key check sends an empty JSON body to `/v1/chat/completions`. Only HTTP 400 with `model is required` proves the gateway accepted the credential before rejecting the body. No model is selected, no inference is forwarded, and nothing is billed. The check consumes a rate-limit slot. HTTP 401 rejects the key; 429 and all other responses are inconclusive. The key-check request has a 15-second timeout and refuse redirects. Catalog discovery has a 30-second timeout. No custom upstream origin can receive your key.
@@ -53,13 +67,13 @@ The public gateway implements Chat Completions and Anthropic Messages, not `/v1/
53
67
 
54
68
  ```sh
55
69
  # Prints instructions only; does not start a server or write Codex settings.
56
- npx @atmorouter/helper@0.2.2 configure --target codex --model YOUR_MODEL_ID
70
+ npx @atmorouter/helper@0.2.3 configure --target codex --model YOUR_MODEL_ID
57
71
 
58
72
  # Starts a temporary local bridge and launches your installed Codex CLI.
59
- npx @atmorouter/helper@0.2.2 codex --model YOUR_MODEL_ID
73
+ npx @atmorouter/helper@0.2.3 codex --model YOUR_MODEL_ID
60
74
 
61
75
  # Or run the bridge separately and follow its printed client instructions.
62
- npx @atmorouter/helper@0.2.2 bridge --port 43187
76
+ npx @atmorouter/helper@0.2.3 bridge --port 43187
63
77
  ```
64
78
 
65
79
  Set `ATMOROUTER_API_KEY` in the process environment before starting the bridge. It listens on loopback only, authenticates local requests, and forwards inference to the official AtmoRouter endpoint. The launcher preserves Codex's permission defaults. It does not weaken approvals or sandbox settings.
@@ -6,7 +6,7 @@ import fs from 'node:fs';
6
6
  import path from 'node:path';
7
7
  import {spawn} from 'node:child_process';
8
8
  import {setDefaultResultOrder} from 'node:dns';
9
- import {models,modelId,checkKey,planConfiguration,writeConfiguration,environment,INTEGRATIONS,guidedConfiguration} from '../lib/helper.mjs';
9
+ import {models,modelId,checkKey,planConfiguration,writeConfiguration,environment,INTEGRATIONS,guidedConfiguration,SETUP_GUIDANCE} from '../lib/helper.mjs';
10
10
  import {startResponsesBridge} from '../lib/bridge.mjs';
11
11
 
12
12
  // Prefer IPv4 when both families are advertised; IPv6-only DNS still works.
@@ -59,6 +59,7 @@ async function launchCodex(model,args){
59
59
  const bridge=await startResponsesBridge({port:0});
60
60
  try {
61
61
  console.log('Starting Codex through a loopback-only bridge. Hosted web search is disabled for this run. Model requests are billed by AtmoRouter.');
62
+ console.error(SETUP_GUIDANCE);
62
63
  const configuration=['-c','model_provider="atmorouter"','-c','model='+JSON.stringify(model),'-c',`model_providers.atmorouter={name="AtmoRouter",base_url="${bridge.url}",env_key="ATMOROUTER_API_KEY",wire_api="responses",requires_openai_auth=false,supports_websockets=false}`,'-c','web_search="disabled"'];
63
64
  const code=await new Promise((resolve,reject)=>{
64
65
  const child=spawn(executable.file,[...executable.args,...configuration,...args],{stdio:'inherit',env:process.env});
@@ -84,6 +85,7 @@ async function main(){
84
85
  if(command==='bridge'){
85
86
  if(!/^\d+$/.test(values.port)||Number(values.port)<1||Number(values.port)>65535)throw Error('Bridge port must be an integer from 1 to 65535.');
86
87
  const bridge=await startResponsesBridge({port:Number(values.port)});
88
+ console.error(SETUP_GUIDANCE);
87
89
  console.log('Local Responses bridge: '+bridge.url+'\nStateless text/tools only; normal AtmoRouter charges apply. Press Ctrl+C to stop.');
88
90
  console.log('In your Codex configuration, set model_provider = "atmorouter" and your chosen model and web_search = "disabled" before provider tables. Then add:\n\n[model_providers.atmorouter]\nname = "AtmoRouter local bridge"\nbase_url = "'+bridge.url+'"\nenv_key = "ATMOROUTER_API_KEY"\nwire_api = "responses"\nrequires_openai_auth = false\nsupports_websockets = false\n\nSet ATMOROUTER_API_KEY privately in the terminal that starts Codex. Run configure --target codex --model YOUR_MODEL_ID for full instructions.');
89
91
  await new Promise(resolve=>{process.once('SIGINT',resolve);process.once('SIGTERM',resolve);});await bridge.close();return;
@@ -118,12 +120,12 @@ async function main(){
118
120
  if(integration.mode==='unavailable')throw Error(guidedConfiguration(target,model,{shell:values.shell}));
119
121
  if(['guided','bridge'].includes(integration.mode)){
120
122
  if(values.write)throw Error('This integration uses guided setup. No configuration files were changed.');
121
- console.log(guidedConfiguration(target,model,{shell:values.shell}));return;
123
+ console.log(guidedConfiguration(target,model,{shell:values.shell}));console.error(SETUP_GUIDANCE);return;
122
124
  }
123
125
  if(!values.offline&&!rl){if(!(await models()).some(m=>m.id===model))throw Error('Model is not in the live text catalog. Use models to choose one.');}
124
126
  if(['openai','anthropic'].includes(target)){
125
127
  if(values.write)throw Error('Environment snippets are preview-only. Copy them into your shell.');
126
- console.log(environment(target,model,values.shell));return;
128
+ console.log(environment(target,model,values.shell));console.error(SETUP_GUIDANCE);return;
127
129
  }
128
130
  const plan=await planConfiguration(target,model,{directory:values['config-dir']});
129
131
  // Print only the intended patch. Existing settings may contain secrets.
@@ -136,8 +138,9 @@ async function main(){
136
138
  if(values.yes)apply=true;
137
139
  else {if(!process.stdin.isTTY)throw Error('Non-interactive writes require --write --yes. Nothing changed.');rl=createInterface({input:process.stdin,output:process.stdout});apply=/^y(es)?$/i.test((await rl.question('Apply these changes? [y/N] ')).trim());}
138
140
  }
139
- if(!apply){console.log('Preview only. No files changed.');return;}
141
+ if(!apply){console.log('Preview only. No files changed.');console.error(SETUP_GUIDANCE);return;}
140
142
  const written=await writeConfiguration(plan);console.log('Saved: '+written.file);if(written.backup)console.log('Backup: '+written.backup);
143
+ console.error(SETUP_GUIDANCE);
141
144
  } finally {rl?.close();}
142
145
  }
143
146
  main().catch(error=>{
package/lib/helper.mjs CHANGED
@@ -6,6 +6,12 @@ import {planYamlConfiguration,assertSafeConfigurationPath} from './integrations.
6
6
  export {INTEGRATIONS,guidedConfiguration} from './integrations.mjs';
7
7
 
8
8
  export const ORIGIN = 'https://atmorouter.dev';
9
+ export const SETUP_GUIDANCE = `Before using your client: review Budgets at https://atmorouter.dev/dashboard/budgets.
10
+ Market prices and tiers vary. A "From" price is a snapshot of the cheapest advertised offer, not a guaranteed execution price.
11
+ Select the actual API key used by this client and an all-model percentage rule for broad coverage. Illustrative examples only: 20% below official rates, or 50% below official rates; choose your own limit after reviewing the rates. Account and key rules both apply; the strictest input and output limits win. A rule for one model does not cover other models. The helper never saves or raises budget rules automatically.
12
+ Routing can wait up to 30 seconds for capacity within your caps. This wait is fixed; generation time is additional. Allow both in your client timeout. The local bridge has a 120-second total timeout by default.
13
+ For 429 or temporary availability failures, honor Retry-After when present; otherwise use bounded exponential backoff with jitter. Do not retry blindly or duplicate an in-flight request. After a timeout or interrupted stream, review the client outcome and usage before deciding to resend. Never raise price caps automatically.
14
+ For invalid requests, correct the request; for authentication errors, check the key and account eligibility; for insufficient balance, review Billing. A price_limit_unavailable refusal needs capacity within your caps or a deliberate budget review; adding funds does not fix pricing availability. Client setup and key checks do not prove current availability within your limits.`;
9
15
  const isRecord = value => value !== null && typeof value === 'object' && !Array.isArray(value);
10
16
  export function modelId(value) {
11
17
  if (typeof value!=='string' || value!==value.trim() || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,200}$/.test(value)) throw Error('Choose a model ID from the catalog; shell characters and whitespace are not allowed.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atmorouter/helper",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Configure AtmoRouter coding agents, guide IDE setup, and run a local Codex bridge",
5
5
  "homepage": "https://atmorouter.dev/docs/helper",
6
6
  "keywords": [