kira-wallet 0.1.4 → 0.1.6

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
@@ -140,3 +140,14 @@ From source, use `node bin/kira.cjs` in place of `kira`.
140
140
  Original code and Kira character assets are available under the [MIT license](LICENSE).
141
141
  Third-party artwork and references retain their own terms; see
142
142
  [Third-party notices](THIRD_PARTY_NOTICES.md).
143
+
144
+ ### Token coverage and optional Alchemy setup
145
+
146
+ Kira uses free public RPC with bounded fallback and checks a selected common-token
147
+ list alongside native assets and Mint Club. Other ERC20 holdings can be missing.
148
+ Incomplete results show recovery guidance on Home and the wallet page. Connect
149
+ your own Alchemy account for broader discovery on supported networks, then
150
+ refresh holdings. Public RPC remains the first choice for ordinary reads;
151
+ Alchemy is a backup and supplies the dedicated token inventory API. See the
152
+ [step-by-step Alchemy guide](docs/ALCHEMY_SETUP.md) and
153
+ [RPC behavior](docs/FREE_RPC.md).
@@ -0,0 +1,95 @@
1
+ # Connect Alchemy to Kira
2
+
3
+ Kira works with free public RPC by default. It checks native assets, selected
4
+ common token contracts and the Mint Club registry. Other ERC20 holdings may
5
+ be missing. For broader token discovery, connect your own Alchemy account.
6
+ Kira runs these requests locally and does not operate a hosted relay.
7
+
8
+ ## Create an Alchemy app
9
+
10
+ 1. Sign in to the [Alchemy dashboard](https://dashboard.alchemy.com/).
11
+ A new account may already have a default app. Use it, or open your team
12
+ menu, Team Overview, Apps, then Create new app.
13
+ 2. Name the app and select the mainnets you want to research. Check that the
14
+ app has Node API and Portfolio API access for those networks. Create it.
15
+ 3. Copy the app API key. The Endpoints tab shows its network URLs. Kira's
16
+ combined Alchemy setup needs the key, so you do not need to copy each URL.
17
+
18
+ These steps follow Alchemy's [official API key guide](https://www.alchemy.com/docs/create-an-api-key),
19
+ checked on October 9, 2026. Dashboard wording can change.
20
+
21
+ ## Connect the key locally
22
+
23
+ Create a private directory and file in your terminal:
24
+
25
+ ```sh
26
+ mkdir -p ~/.config/kira
27
+ touch ~/.config/kira/alchemy.env
28
+ chmod 600 ~/.config/kira/alchemy.env
29
+ ```
30
+
31
+ Open the file in a local text editor. On macOS, you can use:
32
+
33
+ ```sh
34
+ open -e ~/.config/kira/alchemy.env
35
+ ```
36
+
37
+ Add this line, replacing `YOUR_KEY` inside the editor:
38
+
39
+ ```dotenv
40
+ ALCHEMY_API_KEY=YOUR_KEY
41
+ ```
42
+
43
+ Save the file, then run:
44
+
45
+ ```sh
46
+ kira config import-env --file ~/.config/kira/alchemy.env
47
+ ```
48
+
49
+ For a non-default workspace, supply its existing `--data-dir` before `config`.
50
+ Kira stores the file reference. Credentials stay outside the browser, chat and
51
+ published analysis. Do not commit this file to a repository. A process
52
+ environment variable with the same name overrides the file value; check that
53
+ if an old key remains active.
54
+
55
+ ## Check the connection and refresh
56
+
57
+ Open Workspace settings, then Manage data connection. Select Alchemy and use
58
+ "I added my key · check again". Save the connection. Finding the local key
59
+ does not prove the account, API or network is working.
60
+
61
+ For an existing wallet, choose Refresh this wallet's holdings or Refresh
62
+ holdings on its page. A new analysis confirms access and coverage. Old saved
63
+ analyses retain their original observations and warnings. Saving a connection
64
+ does not register a wallet or start research automatically.
65
+
66
+ ## What uses the key
67
+
68
+ Ordinary balances prefer public RPC. After two failed or unavailable public
69
+ providers, Kira tries the configured custom backup within the read deadline.
70
+ Advanced settings can choose custom first or require custom only. Existing
71
+ explicit custom-only restrictions remain in force.
72
+
73
+ Alchemy token discovery uses the key directly. Standard public RPC cannot
74
+ enumerate arbitrary ERC20 holdings. Kira uses Alchemy's
75
+ [Tokens By Wallet endpoint](https://www.alchemy.com/docs/data/portfolio-apis/portfolio-api-endpoints/portfolio-api-endpoints/get-tokens-by-address),
76
+ then verifies discovered candidates with on-chain balance reads.
77
+
78
+ ## If tokens are still missing
79
+
80
+ - **Local key unavailable:** confirm the file exists, its variable name matches
81
+ the connection settings, and you ran the import command in the right workspace.
82
+ - **Discovery failed:** check the app's enabled APIs and networks and Alchemy's
83
+ usage dashboard. Correct access or limits, then retry holdings.
84
+ - **Some balances could not be checked:** wait briefly and retry. Public and
85
+ custom providers both have outages and limits. Review Advanced read preferences
86
+ if a custom-only endpoint is unavailable.
87
+ - **Unsupported network:** broader discovery is unavailable there. Connecting
88
+ Alchemy cannot cover every network or every token. Known-contract reads remain
89
+ useful, and missing holdings remain unknown.
90
+
91
+ Alchemy has account allowances and throughput limits. Check the current
92
+ [plans](https://www.alchemy.com/docs/reference/pricing-plans) and API availability
93
+ before selecting a plan. Kira does not create an account, subscribe or upgrade
94
+ it. Public-first balance reads reduce ordinary use of the key; discovery still
95
+ consumes Alchemy allowance. No connection guarantees a complete token inventory.
package/docs/CLI.md CHANGED
@@ -10,7 +10,7 @@ npm install -g kira-wallet
10
10
  kira setup
11
11
  ```
12
12
 
13
- The operator published npm version 0.1.3 on 2026-10-06. GitHub v0.1.3 remains
13
+ The operator published npm version 0.1.5. Version 0.1.6 is a preparation candidate. GitHub v0.1.3 remains
14
14
  a draft; the previous archive and checksum are available from the
15
15
  [0.1.2 GitHub release](https://github.com/realproject7/kira-wallet/releases/tag/v0.1.2).
16
16
  Registry publication remains an operator action. The code and original character assets use the MIT license.
@@ -42,14 +42,14 @@ address or use the browser wallet picker. Registration starts one explicit
42
42
  research job; follow it in Activity and read the saved portfolio report.
43
43
  No signature, seed phrase or private key is needed.
44
44
 
45
- Candidate 0.1.4 explains limited discovery before wallet registration and beside
46
- holdings. Missing coverage keeps the portfolio estimate partial. It is not yet
47
- published to npm. Existing snapshots require a holdings refresh after connecting
45
+ Published 0.1.4 explains limited discovery before wallet registration and beside
46
+ holdings. Missing coverage keeps the portfolio estimate partial. Existing snapshots require a holdings refresh after connecting
48
47
  an indexer; a price refresh does not discover missing tokens.
49
48
 
50
49
  Public RPC checks known assets and Mint Club. For broader ERC20 discovery,
51
- open Research connections, select Alchemy and name the private environment
52
- variable holding its key. Never paste the key into the browser. `kira doctor`
50
+ open Custom RPC & token coverage, select Alchemy and follow its local key guide.
51
+ Published 0.1.5 uses one save for Alchemy RPC and discovery; chain overrides
52
+ remain under Advanced. Never paste the key into the browser. `kira doctor`
53
53
  checks whether the configured key is available. Configuration alone does not
54
54
  prove complete coverage; inspect the recorded results and gaps.
55
55
 
@@ -68,8 +68,12 @@ authentication. Verified adapter versions are Codex 0.158.x and Claude Code
68
68
  verified. This is a native CLI connection, with no Kira account, API-key proxy or
69
69
  hosted portfolio backend.
70
70
 
71
- In setup, choose the CLI account and an optional model ID. The blank model field
72
- uses the CLI default. Choose no automatic wallet context, one registered wallet,
71
+ From published 0.1.5, setup opens the existing Account / Permissions / Ready
72
+ modal directly. Choose Codex or Claude, then Use default model or a readable
73
+ model choice. Codex choices come from bounded native metadata; Claude offers
74
+ Sonnet, Opus and Haiku aliases. A catalog is not proof of account access.
75
+ Advanced accepts a custom identifier and preserves saved identifiers absent
76
+ from the catalog. Workspace settings reopens the same selector and permissions. Choose no automatic wallet context, one registered wallet,
73
77
  or the whole recorded portfolio. A generic response check sends no wallet data
74
78
  and must pass before these settings are saved.
75
79
 
@@ -79,7 +83,10 @@ and contract identities, timestamps, unknown prices and coverage. Credentials,
79
83
  RPC endpoints, raw provider errors and local paths are excluded. Changing the
80
84
  provider, model, context or selected wallet creates a new conversation.
81
85
 
82
- Kira-local history is opt-in and uses private `conversations/` files. With history
86
+ New setup in 0.1.5 recommends saving history and preselects it. Existing saved
87
+ true and false choices are restored exactly. Saving lets you reopen chats after
88
+ restart; it does not expand model memory or wallet access. The choice and
89
+ research permission remain editable in Workspace settings. Kira-local history uses private `conversations/` files. With history
83
90
  off, conversations and drafts stay in memory. A new server process starts a fresh
84
91
  conversation; saved files remain available locally and are not automatically
85
92
  replayed. Native CLI, administrator policy and provider data policies remain a
@@ -135,8 +142,8 @@ kira config import-env --file /absolute/private/rpc.env --key-env ALCHEMY_API_KE
135
142
  ```
136
143
 
137
144
  The import references the file instead of copying keys. It configures Alchemy
138
- RPC and portfolio discovery separately, with public fallback enabled to match
139
- the historical engine. Environment overrides the file. Missing files or keys
145
+ RPC and portfolio discovery together, preserving existing custom chain overrides
146
+ and explicit fallback preferences. New configurations allow public fallback. Environment overrides the file. Missing files or keys
140
147
  produce limited coverage; malformed configuration fails with safe errors.
141
148
  The import does not contact providers. Private settings are never served.
142
149
 
@@ -205,3 +212,12 @@ in session storage. It clears password fields immediately. Recovery keeps the
205
212
  original encryption passphrase. No signing, key export or account deletion
206
213
  endpoint is exposed. OWS absence does not block ordinary address or browser
207
214
  wallet onboarding.
215
+
216
+ ## Free reads and broader token coverage
217
+
218
+ Public RPC is preferred for ordinary reads, including when a custom backup is
219
+ configured. Advanced settings can select custom first or custom only. For CLI
220
+ configuration, use `kira rpc set --chain 8453 --url-env KIRA_BASE_RPC --priority
221
+ public_first`. Existing custom-only choices remain unchanged. Alchemy token
222
+ discovery uses the configured key directly. See [free RPC behavior](FREE_RPC.md)
223
+ and the [Alchemy setup guide](ALCHEMY_SETUP.md).
@@ -0,0 +1,85 @@
1
+ # Free RPC reads and token coverage
2
+
3
+ Kira contacts public providers directly from the local research process. No
4
+ Kira relay or shared credential is required. Free public services are best
5
+ effort and can throttle requests or omit historical state.
6
+
7
+ The reviewed default catalog adds alternatives on Ethereum, Base, BNB Chain,
8
+ Polygon, Arbitrum and Optimism. PublicNode is the initial preference. Reserves
9
+ are selected per chain rather than downloaded from an untrusted runtime list.
10
+ The other supported networks retain their existing endpoints.
11
+
12
+ ## Request behavior
13
+
14
+ - Chain identity is verified once per endpoint session and again after recovery.
15
+ - One request runs per endpoint. Provider families share one queue and a
16
+ conservative two-second interval across chains. Two chain scans run at once.
17
+ - A logical read has a twelve-second deadline including queues and verification.
18
+ The preferred HTTP attempt has four seconds, reserves at most two seconds.
19
+ Public attempts also have a total budget including verification and pacing,
20
+ leaving half the deadline for a configured personal backup.
21
+ Real fetches are aborted when a deadline expires. HTTP automatic retries are off.
22
+ - At most four endpoint attempts run for a read. Public-first configurations
23
+ reserve an attempt for the personal backup after two public providers.
24
+ - HTTP 429 and JSON-RPC backpressure cool an endpoint for at least sixty seconds.
25
+ Consecutive throttles increase this to at most thirty minutes. Retry-After
26
+ can extend the wait. Other transport failures have a short cooldown.
27
+ - Unsupported methods have a method-specific penalty. Contract reverts do not
28
+ cool an otherwise healthy endpoint. Oversized Multicalls can split; outages and
29
+ throttling cannot create a burst of smaller calls.
30
+
31
+ These are application limits, not promises about a provider's quota or uptime.
32
+ Cooldown observations survive sequential research subprocesses in a bounded
33
+ local cache. Custom endpoints and credentialed URLs are not written there.
34
+ Status screens and onboarding checks do not probe RPC providers.
35
+
36
+ ## Snapshot consistency
37
+
38
+ Each chain analysis records a block number and hash. Balance and contract reads
39
+ keep that block. Before a fallback provider reads it, Kira verifies its header
40
+ has the same hash. Every evidence read then uses an EIP-1898 block-hash selector
41
+ with canonical checking. Providers that do not support it cannot supply that
42
+ snapshot's evidence. Conflicting or unavailable evidence stays incomplete.
43
+ No fallback silently substitutes `latest` for a fixed block.
44
+
45
+ Identical concurrent reads share work. Successful fixed-block reads have a
46
+ bounded cache keyed by chain, pinned hash, method and complete parameters,
47
+ including the wallet address. Failed reads are evicted and never become zero.
48
+ Wallet balances are not reused across wallets or new research processes.
49
+ Registry identity caching remains separate from balances.
50
+
51
+ ## Default token inventory
52
+
53
+ Native balances and the selected issuer/deployment-backed contracts in
54
+ `sources/known-tokens.json` are checked before cold Mint Club enumeration.
55
+ The small list includes USDC on seven supported mainnets, USDT on Ethereum and
56
+ Avalanche, and WETH on Arbitrum. Decimals and quantities come from on-chain
57
+ reads. This list does not cover all common tokens or all wrapped/bridged variants.
58
+
59
+ The full Mint Club registry is still scanned. Public RPC cannot enumerate all
60
+ arbitrary ERC20 contracts held by an address. Discovery remains incomplete
61
+ without a working indexer on that network. Incomplete holdings show a persistent
62
+ notice on Home and the wallet page, with a path to
63
+ [Alchemy setup](ALCHEMY_SETUP.md) and an explicit holdings refresh.
64
+
65
+ ## Read preferences
66
+
67
+ Public first is the default even when a custom connection is configured.
68
+ Advanced settings provide custom first and custom only. Configurations without
69
+ the new priority field use public first; an existing explicit custom-only choice
70
+ continues to exclude public endpoints. RPC priority does not change the dedicated
71
+ Alchemy discovery request.
72
+
73
+ CLI example using an existing local endpoint variable:
74
+
75
+ ```sh
76
+ kira rpc set --chain 8453 --url-env KIRA_BASE_RPC --priority public_first
77
+ ```
78
+
79
+ Primary references: [EIP-1898](https://eips.ethereum.org/EIPS/eip-1898),
80
+ [PublicNode](https://www.publicnode.com/),
81
+ [dRPC limits](https://drpc.org/docs/howitworks/ratelimiting),
82
+ [1RPC networks](https://docs.1rpc.io/overview/supported-networks),
83
+ [Circle contracts](https://developers.circle.com/stablecoins/usdc-contract-addresses),
84
+ [Tether contracts](https://tether.to/en/supported-protocols/),
85
+ [Uniswap Arbitrum deployments](https://developers.uniswap.org/docs/protocols/v3/deployments/v3-arbitrum-deployments).
@@ -8,7 +8,8 @@ this contract. Chat interruption never cancels an engine job.
8
8
  Mutations use `{schema_version: 1, operation, input, idempotency_key}`.
9
9
  Supported operations are `wallet.add` (address, exact tag), `wallet.refresh`
10
10
  (wallet address or exact tag), `prices.refresh` (same), `wallet.setTags`
11
- (wallet, exact tags), `job.resume` and `job.cancel` (job_id).
11
+ (wallet, exact tags), `settings.rpc`, `settings.discovery`, `settings.provider`,
12
+ `job.resume` and `job.cancel` (job_id).
12
13
  Read operations are `job.read`, `job.list`, `snapshot.read`, and
13
14
  `snapshots.compare`. The portfolio projection retains its existing schema.
14
15
 
@@ -21,6 +22,17 @@ the same job without rerunning providers. Resume requeues the same job ID and
21
22
  increments its attempt on execution. Retain the key until the job is removed
22
23
  by a future explicit retention policy. Version mismatches fail closed.
23
24
 
25
+ `settings.provider` accepts exactly `{provider: "public" | "alchemy", key_env}`.
26
+ The key reference is a bounded environment variable name, never a credential.
27
+ One writer-locked atomic replacement changes RPC mode and discovery together.
28
+ Alchemy updates generated network references while preserving explicit custom
29
+ chain overrides, unrelated chains, explorer choice and public fallback policy.
30
+ A missing local key fails before replacement. Public mode keeps inactive custom
31
+ references for later use. A detected key is not a validated provider or complete
32
+ holdings result. Existing settings.rpc and settings.discovery requests remain
33
+ compatible. The browser waits for terminal settings results; retrying a lost
34
+ result checks the same accepted job instead of submitting another change.
35
+
24
36
  ## Envelope
25
37
 
26
38
  Each private `jobs/<uuid>.json` records schema_version, job_id, operation,
@@ -4,6 +4,27 @@ Kira connects the user's installed, unmodified Codex or Claude Code CLI. It does
4
4
 
5
5
  The setup flow separates account readiness, model choice, context permission, and confirmation. The conversation composer remains visible. It shows the connected CLI, automatic context scope, response state, cancellation, and a new conversation action. Enter sends; Shift+Enter adds a line; composition events never send.
6
6
 
7
+ `kira setup` opens the existing Account, Permissions and Ready modal directly
8
+ after the local session and workspace are available. Opening setup does not run
9
+ a model response check, save permissions or start wallet research. Existing
10
+ settings are loaded when setup is reopened. Closing the modal consumes that
11
+ page's setup request, so background polling does not reopen it. Ordinary
12
+ `kira start` keeps the normal workspace entry behavior.
13
+
14
+ The 0.1.5 selector defaults to Use default model. Codex uses a bounded stdio
15
+ app-server handshake and model/list request; it never starts a thread or turn.
16
+ The subprocess has an eight-second metadata deadline and 256 KB output bound.
17
+ Metadata failures leave the default and Advanced identifier entry available.
18
+ Only installed supported CLI versions are queried. Claude uses its documented
19
+ Sonnet, Opus and Haiku aliases. Every chosen model still requires a deliberate
20
+ generic response test. Vendor account and managed policies may restrict access.
21
+ Workspace settings reuses this modal for both providers and all permissions.
22
+ Fresh setup preselects local history; existing false values remain false.
23
+ History permits reopening chats, without extending model memory or scope.
24
+ No wallet context permits general chat and manual wallet management while
25
+ wallet context and research tools remain unavailable. Any effective model or
26
+ permission change starts a fresh conversation.
27
+
7
28
  Automatic context is none, one explicitly selected registered wallet, or the whole recorded portfolio. An allowlisted projection preserves exact balances, contract and chain identities, timestamps, unknown values, and coverage gaps. It excludes credentials, RPC endpoints, paths, raw errors, jobs, and provider payloads. User-entered text is also sent to the selected provider. Scope, wallet, provider, or model changes start a new conversation and never replay old messages. Only a bounded current conversation is replayed.
8
29
 
9
30
  Approved wallet context can include validated historical full-balance Mint Club
@@ -53,7 +74,7 @@ in Activity and also appear in the chat tray. Unanalysed wallets show a sidebar
53
74
  spinner and open Activity while their first holdings job is active.
54
75
 
55
76
  History is capped at 100 memory sessions and reads up to 200 validated local
56
- files. Saving remains opt-in. Memory sessions disappear when the process stops.
77
+ files. Saving is recommended for fresh setup and remains an editable choice. Memory sessions disappear when the process stops.
57
78
  A previous session can continue only with exactly the same model and context
58
79
  configuration. Reading history does not expand model access. Old saved files
59
80
  remain readable. No missing memory-only conversations are reconstructed.
@@ -65,3 +86,15 @@ The receipt is consumed only on success. This does not enable history retention.
65
86
  The Kira system voice is concise, composed and practical. It leads with findings
66
87
  and preserves evidence, unknown values and signing boundaries. Personality is
67
88
  prompt guidance; response quality still depends on the selected model.
89
+
90
+ ## 0.1.5 preparation evidence
91
+
92
+ The combined settings and model-selection tests cover catalog failures, stale
93
+ responses, custom identifiers, fresh retention defaults, saved opt-outs,
94
+ atomic provider changes, preference preservation, missing keys, cancellation
95
+ and ambiguous receipts. Desktop 1440 × 900 and mobile 390 × 844 checks used an
96
+ empty disposable workspace. Codex 0.158.0 and Claude Code 2.1.293 completed
97
+ native default-model response checks with no wallet data. The provider save
98
+ used a synthetic local key reference and made no provider research request.
99
+ Real extension approval, personal OWS passphrases and live holdings coverage
100
+ are separate operator acceptance. Published 0.1.4 snapshots are not rewritten.
package/docs/RELEASE.md CHANGED
@@ -32,7 +32,7 @@ creation now identifies Open Wallet Standard with its official locally served
32
32
  logo and explains encrypted local storage and public-account research. The
33
33
  OWS integration and wallet-operation permissions are unchanged.
34
34
 
35
- The next candidate is **0.1.4**. It makes incomplete token discovery visible
35
+ Published **0.1.4** makes incomplete token discovery visible
36
36
  before registration and beside wallet holdings, separates disabled discovery
37
37
  from missing credentials and provider failures, and offers a connection or
38
38
  holdings-refresh action. Registry failures retain bounded redacted examples
@@ -44,10 +44,21 @@ RPC failures show affected networks, retry guidance and a settings action.
44
44
  Failed candidate balance reads keep the saved result and valuation partial
45
45
  even when general token discovery succeeded.
46
46
 
47
- Version 0.1.4 requires independent review and operator registry publication.
48
- It is not yet available from the registry. The showcase HTML animation is
49
- deployed separately and requires no npm release. Keep published 0.1.2 and
50
- 0.1.3 package bytes immutable. See [discovery acceptance](KIRA_DISCOVERY_ACCEPTANCE.md).
47
+ Published **0.1.5** opens the existing setup modal directly, adds Codex and
48
+ Claude model choices, exposes model and permission changes in Workspace settings,
49
+ and provides one Alchemy connection save. Its registry version was confirmed on
50
+ 2026-10-09. Published package bytes remain immutable.
51
+
52
+ The next candidate is **0.1.6**. It adds a paced local RPC pool, core-chain public
53
+ alternatives, public-first custom backup, fixed-block hash checks and a small
54
+ known-token catalog. Home and wallet pages display incomplete coverage with an
55
+ Alchemy setup action. The settings guide explains account setup, private local
56
+ key import and an explicit holdings refresh. Missing or malformed discovery data
57
+ cannot become a completed empty inventory.
58
+
59
+ Version 0.1.6 requires review and operator registry publication. It is not yet
60
+ published. The showcase remains a separate deployment. See
61
+ [free RPC behavior](FREE_RPC.md) and [Alchemy setup](ALCHEMY_SETUP.md).
51
62
 
52
63
  The operator published npm **0.1.2** on 2026-10-05. Its registry archive was
53
64
  downloaded and matched the reviewed GitHub archive above, including SHA-256,
@@ -69,8 +80,7 @@ The earlier unpublished 0.1.2 candidate is preserved in the ignored
69
80
  `dist/preserved-0.1.2-bad8f02/` directory. Use the current `dist/release.json`
70
81
  and its matching archive for publication; do not mix manifests from candidates.
71
82
 
72
- Keep GitHub v0.1.1, its tag and its released archive immutable. Versions 0.1.2
73
- and 0.1.3 are also published and immutable. Do not rebuild them from later documentation
83
+ Keep GitHub v0.1.1, its tag and its released archive immutable. Versions 0.1.2, 0.1.3, 0.1.4 and 0.1.5 are also published and immutable. Do not rebuild them from later documentation
74
84
  commits. Future package changes need a new version. Release preparation does not
75
85
  publish a GitHub release, merge PR 19, promote the website or publish to npm.
76
86
  Registry publication remains operator-only. The coding agent never publishes.
@@ -85,12 +95,12 @@ python3 scripts/prepare-release.py
85
95
  ```
86
96
 
87
97
  `prepare-release.py` scans the package boundary and creates
88
- `dist/kira-wallet-0.1.4.tgz`. The ignored `dist/release.json` records its source
98
+ `dist/kira-wallet-0.1.6.tgz`. The ignored `dist/release.json` records its source
89
99
  commit, SHA-256 and member count. It never publishes. After reviewing that
90
100
  manifest, the operator's final command from the project root is:
91
101
 
92
102
  ```sh
93
- npm publish ./dist/kira-wallet-0.1.4.tgz --access public
103
+ npm publish ./dist/kira-wallet-0.1.6.tgz --access public
94
104
  ```
95
105
 
96
106
  Use the operator's npm account and complete authentication or OTP personally.
package/kira_agent.py CHANGED
@@ -251,6 +251,7 @@ class AgentStore:
251
251
  self.root = Path(root);self.path = self.root/'.kira-agent.json';self.runner=runner;self.detector=detector
252
252
  self.lock=threading.RLock();self.turns={};self.active=None;self.epoch=0;self.messages=[];self.conversation=str(uuid.uuid4());self.tested=set();self.config=None;self.workers=[];self.tool_status=None
253
253
  self._capabilities=None;self._checked=0
254
+ self._model_lock=threading.Lock();self._model_cache={}
254
255
  self.sessions={};self.started_at=datetime.now(timezone.utc).isoformat();self.updated_at=self.started_at
255
256
  if self.path.exists():
256
257
  try: self.config=config_input(json.loads(self.path.read_text()),self.root)
@@ -344,6 +345,19 @@ class AgentStore:
344
345
  self._capabilities=self.detector();self._checked=time.monotonic()
345
346
  with self.lock:return {'config':self.config,'providers':self._capabilities,'conversation_id':self.conversation,'messages':self.messages.copy(),'active_turn':self.active,'tool_status':self.tool_status}
346
347
 
348
+ def models(self, provider, recheck=False):
349
+ if provider not in PROVIDERS:raise JobError('invalid_provider','Choose Codex or Claude.')
350
+ with self._model_lock:
351
+ row=next((row for row in self.status(recheck)['providers'] if row['id']==provider),None)
352
+ if not row or not row['installed'] or not row['supported']:
353
+ return {'models':[], 'note':'Install a supported CLI to load model choices. The default model remains available.'}
354
+ cached=self._model_cache.get(provider)
355
+ if not recheck and cached and time.monotonic()-cached[0]<60:return cached[1]
356
+ from kira_models import catalog
357
+ result=catalog(provider)
358
+ self._model_cache[provider]=(time.monotonic(),result)
359
+ return result
360
+
347
361
  def fingerprint(self,config):return hashlib.sha256(json.dumps(config,sort_keys=True).encode()).hexdigest()
348
362
 
349
363
  def stop(self):
package/kira_cli.py CHANGED
@@ -172,7 +172,7 @@ def main():
172
172
  imp=configsub.add_parser('import-env');imp.add_argument('--file',required=True);imp.add_argument('--key-env',default='ALCHEMY_API_KEY')
173
173
  rpc=sub.add_parser('rpc');rpcsub=rpc.add_subparsers(dest='setting',required=True)
174
174
  rpcsub.add_parser('public')
175
- custom=rpcsub.add_parser('set');custom.add_argument('--chain',type=int,required=True);custom.add_argument('--url-env',required=True);fallback=custom.add_mutually_exclusive_group();fallback.add_argument('--public-fallback',action='store_true',default=None);fallback.add_argument('--custom-only',dest='public_fallback',action='store_false')
175
+ custom=rpcsub.add_parser('set');custom.add_argument('--chain',type=int,required=True);custom.add_argument('--url-env',required=True);custom.add_argument('--priority',choices=['public_first','custom_first']);fallback=custom.add_mutually_exclusive_group();fallback.add_argument('--public-fallback',action='store_true',default=None);fallback.add_argument('--custom-only',dest='public_fallback',action='store_false')
176
176
  discovery=sub.add_parser('discovery');discovery.add_argument('provider',choices=['none','alchemy']);discovery.add_argument('--key-env',default='ALCHEMY_API_KEY');discovery.add_argument('--explorers',action='store_true')
177
177
  args=parser.parse_args();root=Path(args.data_dir).expanduser().resolve();os.environ['KIRA_DATA_DIR']=str(root)
178
178
  if sys.version_info<(3,11):raise ValueError('Python 3.11 or newer is required.')
@@ -232,14 +232,14 @@ def main():
232
232
  if args.command=='config':
233
233
  file=Path(args.file).expanduser().resolve()
234
234
  if not file.is_file():raise ValueError('Secret environment file does not exist.')
235
- from wallet import ALCHEMY
236
- cfg['secret_env_file']=str(file);cfg['discovery']={'provider':'alchemy','key_env':args.key_env,'explorers':True}
237
- cfg['rpc']={'mode':'custom','allow_public_fallback':True,
238
- 'chains':{str(cid):{'url_env':args.key_env,'alchemy_network':name} for cid,name in ALCHEMY.items()}}
235
+ from kira_config import provider_config
236
+ cfg=provider_config(cfg,'alchemy',args.key_env)
237
+ cfg['secret_env_file']=str(file);cfg['discovery']['explorers']=True
239
238
  elif args.command=='discovery':cfg['discovery']={'provider':args.provider,'key_env':args.key_env,'explorers':args.explorers}
240
239
  elif args.setting=='public':cfg['rpc']['mode']='public'
241
240
  else:
242
241
  cfg['rpc'].update(mode='custom')
242
+ if args.priority:cfg['rpc']['priority']=args.priority
243
243
  if args.public_fallback is not None:cfg['rpc']['allow_public_fallback']=args.public_fallback
244
244
  cfg['rpc']['chains'][str(args.chain)]={'url_env':args.url_env}
245
245
  # Validate before replacing the user's working configuration.
package/kira_config.py CHANGED
@@ -16,9 +16,22 @@ def config_path():
16
16
  return Path(os.environ.get('KIRA_CONFIG', data_root()/'.kira.local.json')).expanduser()
17
17
 
18
18
  def default_config():
19
- return {'schema_version':1,'rpc':{'mode':'public','allow_public_fallback':True,'chains':{}},
19
+ return {'schema_version':1,'rpc':{'mode':'public','allow_public_fallback':True,'priority':'public_first','chains':{}},
20
20
  'discovery':{'provider':'none','key_env':'ALCHEMY_API_KEY','explorers':False}}
21
21
 
22
+ def provider_config(config, provider, key_env):
23
+ """One settings transition. Preserve explicit fallback and custom overrides."""
24
+ result={**config,'rpc':{**config['rpc'],'chains':{cid:dict(row) for cid,row in config['rpc']['chains'].items()}},
25
+ 'discovery':{**config['discovery'],'provider':'alchemy' if provider=='alchemy' else 'none','key_env':key_env}}
26
+ result['rpc']['mode']='custom' if provider=='alchemy' else 'public'
27
+ if provider=='alchemy':
28
+ from wallet import ALCHEMY
29
+ for cid,network in ALCHEMY.items():
30
+ previous=result['rpc']['chains'].get(str(cid))
31
+ if not previous or previous.get('alchemy_network'):
32
+ result['rpc']['chains'][str(cid)]={'url_env':key_env,'alchemy_network':network}
33
+ return result
34
+
22
35
  def load_config():
23
36
  path=config_path()
24
37
  if not path.exists():return default_config()
@@ -31,7 +44,8 @@ def load_config():
31
44
  raise ValueError('Invalid RPC configuration.')
32
45
  if not isinstance(discovery,dict) or discovery.get('provider') not in ('none','alchemy') or type(discovery.get('explorers',False)) is not bool:
33
46
  raise ValueError('Invalid discovery configuration.')
34
- if set(rpc)-{'mode','allow_public_fallback','chains'} or set(discovery)-{'provider','key_env','explorers'}:raise ValueError('Unknown provider configuration fields.')
47
+ if rpc.get('priority','public_first') not in ('public_first','custom_first'):raise ValueError('Invalid RPC priority.')
48
+ if set(rpc)-{'mode','allow_public_fallback','priority','chains'} or set(discovery)-{'provider','key_env','explorers'}:raise ValueError('Unknown provider configuration fields.')
35
49
  if any(not isinstance(row,dict) or set(row)-{'url_env','alchemy_network'} for row in rpc['chains'].values()):raise ValueError('Unknown RPC endpoint fields.')
36
50
  for name in [discovery.get('key_env'),*[row.get('url_env') if isinstance(row,dict) else None for row in rpc['chains'].values()]]:
37
51
  if not isinstance(name,str) or not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*',name):raise ValueError('Credentials must use valid environment references.')
@@ -84,7 +98,8 @@ def endpoints(network,cfg=None):
84
98
  # New setups default to automatic fallback. Preserve an explicit existing
85
99
  # opt-out for operators who require custom providers only.
86
100
  if not rpc['allow_public_fallback']:return urls
87
- urls.extend(network.get('public_rpc') or [])
101
+ public=network.get('public_rpc') or []
102
+ urls=public+urls if rpc.get('priority','public_first')=='public_first' else urls+public
88
103
  return list(dict.fromkeys(urls))
89
104
 
90
105
  def runtime():
@@ -92,7 +107,7 @@ def runtime():
92
107
  try: values=secret_values(cfg)
93
108
  except ValueError: values={}
94
109
  return {'endpoints':{str(n['chain_id']):endpoints(n,cfg) for n in networks},
95
- 'secrets':list(values.values()),'data_root':str(data_root())}
110
+ 'secrets':list(values.values()),'data_root':str(data_root()),'health_file':str(data_root()/'cache/rpc-health.json')}
96
111
 
97
112
  if __name__=='__main__':
98
113
  # Internal child-process channel only. The user CLI never emits this payload.
package/kira_jobs.py CHANGED
@@ -19,7 +19,7 @@ import uuid
19
19
 
20
20
  ASSETS = Path(__file__).resolve().parent
21
21
  State = Literal['queued', 'running', 'interrupted', 'succeeded', 'partial', 'failed', 'cancelled']
22
- Operation = Literal['wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery']
22
+ Operation = Literal['wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery','settings.provider']
23
23
  TERMINAL = {'succeeded', 'partial', 'failed', 'cancelled', 'interrupted'}
24
24
  ADDRESS = re.compile(r'0x[0-9a-fA-F]{40}')
25
25
 
@@ -124,10 +124,11 @@ class JobStore:
124
124
  if not isinstance(job,dict) or type(job.get('schema_version')) is not int or job.get('schema_version') != 1: raise JobError('unsupported_version', 'Unsupported persisted job version.')
125
125
  required={'schema_version','job_id','operation','input','state','attempt','sequence','result','previous_result','key_hash','request_hash','created_at','updated_at','cancel_requested','checkpoint','stage','events','errors','chains'}
126
126
  optional={'raw_hash','control_receipts','control_raw','provider_configuration','started_at'}
127
- if required-set(job) or set(job)-required-optional or job['job_id']!=job_id or job['checkpoint']!='snapshots/jobs/'+job_id or not isinstance(job['operation'],str) or job['operation'] not in {'wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery'} or not isinstance(job['state'],str) or job['state'] not in TERMINAL|{'queued','running'} or any(type(job[k]) is not int or job[k]<0 for k in ('attempt','sequence')) or type(job['cancel_requested']) is not bool or not isinstance(job['input'],dict) or any(not isinstance(job[k],list) for k in ('events','errors')) or not isinstance(job['chains'],dict):
127
+ if required-set(job) or set(job)-required-optional or job['job_id']!=job_id or job['checkpoint']!='snapshots/jobs/'+job_id or not isinstance(job['operation'],str) or job['operation'] not in {'wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery','settings.provider'} or not isinstance(job['state'],str) or job['state'] not in TERMINAL|{'queued','running'} or any(type(job[k]) is not int or job[k]<0 for k in ('attempt','sequence')) or type(job['cancel_requested']) is not bool or not isinstance(job['input'],dict) or any(not isinstance(job[k],list) for k in ('events','errors')) or not isinstance(job['chains'],dict):
128
128
  raise JobError('invalid_envelope','Persisted job validation failed. Preserve the file for recovery.')
129
129
  names={'wallet.add':['address','tag'],'wallet.refresh':['wallet'],'prices.refresh':['wallet'],'wallet.setTags':['wallet','tags'],
130
- 'settings.rpc':['mode','chains','allow_public_fallback'],'settings.discovery':['provider','key_env']}[job['operation']]
130
+ 'settings.rpc':['mode','chains','allow_public_fallback'],'settings.discovery':['provider','key_env'],'settings.provider':['provider','key_env']}[job['operation']]
131
+ if job['operation']=='settings.rpc' and 'priority' in job['input']:names=names+['priority']
131
132
  fields(job['input'],names)
132
133
  wallet=job['input'].get('wallet') or job['input'].get('address')
133
134
  if wallet is not None and (not isinstance(wallet,str) or not ADDRESS.fullmatch(wallet)):raise JobError('invalid_envelope','Persisted wallet identity is invalid.')
@@ -154,8 +155,15 @@ class JobStore:
154
155
  return matches[0]
155
156
 
156
157
  def normalize(self, operation, value):
158
+ if operation == 'settings.provider':
159
+ fields(value,['provider','key_env'])
160
+ if value['provider'] not in ('public','alchemy') or not isinstance(value['key_env'],str) or not re.fullmatch(r'[A-Z_][A-Z0-9_]{0,99}',value['key_env']):
161
+ raise JobError('invalid_settings','Choose a data provider and a local credential reference.')
162
+ return value.copy()
157
163
  if operation == 'settings.rpc':
158
- fields(value,['mode','chains','allow_public_fallback'])
164
+ fields(value,['mode','chains','allow_public_fallback']+(['priority'] if isinstance(value,dict) and 'priority' in value else []))
165
+ if value.get('priority','public_first') not in ('public_first','custom_first'):
166
+ raise JobError('invalid_settings','Choose a valid RPC priority.')
159
167
  if value['mode'] not in ('public','custom') or type(value['allow_public_fallback']) is not bool or not isinstance(value['chains'],list) or len(value['chains'])>30:
160
168
  raise JobError('invalid_settings','Choose a valid RPC mode and credential references.')
161
169
  available={n['chain_id'] for n in read(ASSETS/'sources/rpc-candidates.json')}
@@ -166,7 +174,7 @@ class JobStore:
166
174
  raise JobError('invalid_settings','Use a known chain and a unique environment variable reference.')
167
175
  chains[str(chain['chain_id'])]={'url_env':chain['url_env']}
168
176
  if value['mode']=='custom' and not chains:raise JobError('invalid_settings','Custom RPC requires at least one endpoint reference.')
169
- return {'mode':value['mode'],'chains':chains,'allow_public_fallback':value['allow_public_fallback']}
177
+ return {'mode':value['mode'],'chains':chains,'allow_public_fallback':value['allow_public_fallback'],**({'priority':value['priority']} if 'priority' in value else {})}
170
178
  if operation == 'settings.discovery':
171
179
  fields(value,['provider','key_env'])
172
180
  if value['provider'] not in ('none','alchemy') or not isinstance(value['key_env'],str) or not re.fullmatch(r'[A-Z_][A-Z0-9_]{0,99}',value['key_env']):raise JobError('invalid_settings','Use a supported indexer and environment variable reference.')
@@ -399,11 +407,17 @@ class JobStore:
399
407
  config=load_config()
400
408
  if job['operation']=='settings.rpc':
401
409
  updated={**job['input'],'chains':{key:dict(row) for key,row in job['input']['chains'].items()}}
410
+ updated.setdefault('priority',config['rpc'].get('priority','public_first'))
402
411
  for key,row in updated['chains'].items():
403
412
  previous=config['rpc']['chains'].get(key,{})
404
413
  if row['url_env']==previous.get('url_env') and previous.get('alchemy_network'):row['alchemy_network']=previous['alchemy_network']
405
414
  config['rpc']=updated
406
- else:config['discovery']={**job['input'],'explorers':config['discovery'].get('explorers',False)}
415
+ elif job['operation']=='settings.discovery':config['discovery']={**job['input'],'explorers':config['discovery'].get('explorers',False)}
416
+ else:
417
+ from kira_config import provider_config,secret_values
418
+ config=provider_config(config,**job['input'])
419
+ if job['input']['provider']=='alchemy' and not secret_values(config).get(job['input']['key_env']):
420
+ raise JobError('connection_key_missing','Kira cannot find the local Alchemy key. Follow the connection guide, then retry. Existing settings are unchanged.')
407
421
  atomic(config_path(),config)
408
422
  result={'status':'completed','setting':job['operation']};atomic(self.jobs/'results'/(job['job_id']+'.json'),result);return result
409
423
  env = {**os.environ, 'KIRA_DATA_DIR': str(self.root), 'KIRA_JOB_ID': job['job_id'], 'KIRA_JOB_SNAPSHOT': job['checkpoint'], 'KIRA_ANALYSIS_FD': str(writer.fileno()), 'PYTHONUNBUFFERED': '1'}