kira-wallet 0.2.1 → 0.2.2
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 +1 -1
- package/docs/CLI.md +16 -6
- package/docs/RELEASE.md +38 -14
- package/kira_cli.py +25 -22
- package/kira_config.py +3 -2
- package/kira_jobs.py +26 -11
- package/kira_records.py +1 -1
- package/kira_service.py +9 -3
- package/package.json +1 -1
- package/skills/kira-wallet/SKILL.md +41 -5
- package/viewer/refresh_prices.py +9 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ web app to manage wallets, inspect token markets and follow research. Your
|
|
|
15
15
|
existing AI agent can use the same records and actions through the Kira CLI or
|
|
16
16
|
optional MCP connection, then open the web app beside your conversation.
|
|
17
17
|
|
|
18
|
-
**This branch prepares 0.2.
|
|
18
|
+
**This branch prepares 0.2.2.** Kira now supplies wallet tools and a full-window
|
|
19
19
|
management app. Your agent host manages its own conversations, models, files and
|
|
20
20
|
approvals. Kira does not launch a model CLI or require a model login.
|
|
21
21
|
|
package/docs/CLI.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# CLI and provider configuration
|
|
2
2
|
|
|
3
|
-
Kira 0.2.
|
|
3
|
+
Kira 0.2.2 is a local wallet engine, CLI and web interface. Any capable agent
|
|
4
4
|
can use the CLI; MCP is optional. Your agent host supplies its own model,
|
|
5
5
|
conversation, general tools and approvals. Kira does not start a model process.
|
|
6
|
-
This branch prepares 0.2.
|
|
6
|
+
This branch prepares 0.2.2. Registry publication remains an operator action.
|
|
7
7
|
|
|
8
8
|
## Install and choose a workspace
|
|
9
9
|
|
|
@@ -35,7 +35,9 @@ use the supported CLI for a consistent data root.
|
|
|
35
35
|
|
|
36
36
|
`--json` is accepted anywhere in the command. `info` returns the version,
|
|
37
37
|
data directory, capabilities, connection readiness, saved-data revision, job
|
|
38
|
-
status, UI availability and packaged skill path.
|
|
38
|
+
status, UI availability and packaged skill path. Its `research_connection.status`
|
|
39
|
+
is `ready` when Alchemy is connected and `connection_required` before that.
|
|
40
|
+
`--data-dir` also works before or after the subcommand. Use `--help` for syntax.
|
|
39
41
|
|
|
40
42
|
## Open the local app
|
|
41
43
|
|
|
@@ -90,7 +92,8 @@ reject the import and preserve the configuration.
|
|
|
90
92
|
RPC, discovery and import commands apply synchronously when idle. If research
|
|
91
93
|
owns the writer, they return a queued receipt; `--json` always returns a queued
|
|
92
94
|
receipt without waiting. Inspect the resulting job before reporting the
|
|
93
|
-
configuration applied
|
|
95
|
+
configuration applied; its `result.provider_configuration` lists the applied
|
|
96
|
+
modes and reference names. Safe configuration output contains references, not values.
|
|
94
97
|
|
|
95
98
|
New configurations allow verified public RPC fallback. `--custom-only`
|
|
96
99
|
disables fallback; `--public-fallback` restores it. Existing opt-outs persist.
|
|
@@ -122,7 +125,8 @@ kira prices 'Daily wallet' --idempotency-key prices-daily-001 --json
|
|
|
122
125
|
```
|
|
123
126
|
|
|
124
127
|
The first tag is the display name. Exact names and registered addresses select
|
|
125
|
-
wallets.
|
|
128
|
+
wallets. `add` runs the first full holdings lookup itself; use `refresh` for
|
|
129
|
+
later updates. Reuse an idempotency key only when retrying the same intended request.
|
|
126
130
|
|
|
127
131
|
`add`, `refresh` and `prices` persist a durable job before doing research.
|
|
128
132
|
`--json` or `--enqueue` returns its receipt immediately and starts a separate
|
|
@@ -175,10 +179,16 @@ kira call holdings_query --arguments '{"mode":"search","query":"ETH"}' --json
|
|
|
175
179
|
kira call wallet_read --arguments '{"wallet":"Daily wallet","offset":0,"limit":100}' --json
|
|
176
180
|
kira call token_read --arguments '{"chain_id":8453,"address":"native"}' --json
|
|
177
181
|
kira call snapshots_list --arguments '{"wallet":"Daily wallet","limit":20}' --json
|
|
178
|
-
kira call job_list --arguments '{"state":"
|
|
182
|
+
kira call job_list --arguments '{"state":"active","offset":0,"limit":20}' --json
|
|
179
183
|
kira ui context --json
|
|
180
184
|
```
|
|
181
185
|
|
|
186
|
+
`job_list` accepts `state` groups `all` (default), `active` (queued or
|
|
187
|
+
running), `succeeded` (succeeded or partial), `attention` (interrupted or
|
|
188
|
+
failed) and `cancelled`, or one exact state: `queued`, `running`, `partial`,
|
|
189
|
+
`interrupted` or `failed`. Add `wallet` to limit results to one registered
|
|
190
|
+
wallet.
|
|
191
|
+
|
|
182
192
|
Follow returned `next_offset` values when complete pages are needed. Use
|
|
183
193
|
`holdings_query` for server-computed ranking, counts and aggregates across the
|
|
184
194
|
full inventory. Same-symbol tokens may differ by chain and contract; retain
|
package/docs/RELEASE.md
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
# Release preparation
|
|
2
2
|
|
|
3
|
-
## Prepared release: 0.2.
|
|
3
|
+
## Prepared release: 0.2.2
|
|
4
|
+
|
|
5
|
+
Fixes two agent contract findings from the published 0.2.1 fresh-agent test.
|
|
6
|
+
`job_list` now publishes its accepted `state` values in the tool schema and
|
|
7
|
+
accepts each exact job state, such as `running`, as well as the existing
|
|
8
|
+
groups. Invalid values return the accepted list. Documented `kira call`
|
|
9
|
+
examples are checked against the published schemas. Global `--data-dir` now
|
|
10
|
+
works before or after a subcommand, like `--json`.
|
|
11
|
+
|
|
12
|
+
A fresh Claude Code test of this candidate found four more contract gaps, now
|
|
13
|
+
fixed. `research_connection.status` is `ready` only when Alchemy is connected
|
|
14
|
+
and `connection_required` before that, instead of always `configured`. A
|
|
15
|
+
finished settings job reports the settings it applied, not the ones it
|
|
16
|
+
replaced. Native rows in `wallet_read` keep `address: null`, like
|
|
17
|
+
`holdings_query`. `snapshots_list` rows carry `phase`, so progress saved during
|
|
18
|
+
a running lookup is distinct from finished lookups. The skill now explains
|
|
19
|
+
`kira add`, import side effects, per-operation progress, price refresh
|
|
20
|
+
history, float position values and `head_status`.
|
|
21
|
+
|
|
22
|
+
A second, read-only Claude Code check of the fixed candidate led to three
|
|
23
|
+
more changes. A partial price job now names its reasons in `result.gaps`.
|
|
24
|
+
`kira --help` shows the `kira` program name and a line for every command.
|
|
25
|
+
The skill corrects `head_status`, notes the native `address: null`, the
|
|
26
|
+
100-event job window and older settings receipts. Wallet records, research,
|
|
27
|
+
financial projections and the web app are unchanged.
|
|
28
|
+
|
|
29
|
+
Preserve the published 0.2.1 archive and every earlier archive. Freeze 0.2.2
|
|
30
|
+
from the accepted clean commit with `scripts/prepare-release.py`, dry-run that
|
|
31
|
+
exact archive and leave registry publication to the operator. Review not selected.
|
|
32
|
+
|
|
33
|
+
## Published 0.2.1
|
|
4
34
|
|
|
5
35
|
Includes the external-agent architecture described in the preserved 0.2.0
|
|
6
36
|
candidate below and four approved UI refinements. Short desktop windows can
|
|
@@ -9,16 +39,10 @@ one line, a single wallet card fills its available grid width, and token detail
|
|
|
9
39
|
captions and addresses use larger, darker text. Wallet records, research,
|
|
10
40
|
financial projections, CLI and MCP behavior are unchanged.
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
the
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
windows and multiple wallets, and exact source/archive/installed-member checks.
|
|
17
|
-
Freeze from the accepted clean commit, dry-run the exact archive and leave
|
|
18
|
-
registry publication to the operator. Review not selected.
|
|
19
|
-
|
|
20
|
-
The separate real-device acceptance, fresh public website screenshots and site
|
|
21
|
-
content update remain a follow-up after installation of the new version.
|
|
42
|
+
The operator published 0.2.1. Its archive SHA-256 is
|
|
43
|
+
`aed02bc57c0fec8eec8b8fe8539ddcc45592d7876f04b86045e1d136b51804e0`; the
|
|
44
|
+
installed package and all 93 members matched it. The real-device acceptance
|
|
45
|
+
and website update that followed installation are complete.
|
|
22
46
|
|
|
23
47
|
## Preserved candidate: 0.2.0
|
|
24
48
|
|
|
@@ -135,7 +159,7 @@ The earlier unpublished 0.1.2 candidate is preserved in the ignored
|
|
|
135
159
|
`dist/preserved-0.1.2-bad8f02/` directory. Use the current `dist/release.json`
|
|
136
160
|
and its matching archive for publication; do not mix manifests from candidates.
|
|
137
161
|
|
|
138
|
-
Keep published GitHub tags, release assets and npm versions through 0.1
|
|
162
|
+
Keep published GitHub tags, release assets and npm versions through 0.2.1
|
|
139
163
|
immutable. Do not rebuild them from later documentation commits. Future package
|
|
140
164
|
changes need a new version. Release preparation does not publish a GitHub
|
|
141
165
|
release, promote the website or publish to npm.
|
|
@@ -151,12 +175,12 @@ python3 scripts/prepare-release.py
|
|
|
151
175
|
```
|
|
152
176
|
|
|
153
177
|
`prepare-release.py` scans the package boundary and creates
|
|
154
|
-
`dist/kira-wallet-0.2.
|
|
178
|
+
`dist/kira-wallet-0.2.2.tgz`. The ignored `dist/release.json` records its source
|
|
155
179
|
commit, SHA-256 and member count. It never publishes. After reviewing that
|
|
156
180
|
manifest, the operator's final command from the project root is:
|
|
157
181
|
|
|
158
182
|
```sh
|
|
159
|
-
npm publish ./dist/kira-wallet-0.2.
|
|
183
|
+
npm publish ./dist/kira-wallet-0.2.2.tgz --access public
|
|
160
184
|
```
|
|
161
185
|
|
|
162
186
|
Use the operator's npm account and complete authentication or OTP personally.
|
package/kira_cli.py
CHANGED
|
@@ -186,48 +186,51 @@ def finish_configuration(store,job,*,enqueue=False):
|
|
|
186
186
|
return 0
|
|
187
187
|
|
|
188
188
|
def main():
|
|
189
|
-
parser=argparse.ArgumentParser(description=__doc__)
|
|
189
|
+
parser=argparse.ArgumentParser(prog='kira',description=__doc__)
|
|
190
190
|
parser.add_argument('--version',action='version',version=json.loads((ASSETS/'package.json').read_text())['version'])
|
|
191
191
|
parser.add_argument('--json',action='store_true',help='Return structured output; research commands enqueue without waiting.')
|
|
192
|
-
parser.add_argument('--data-dir',default=os.environ.get('KIRA_DATA_DIR',str(Path.home()/'.local/share/kira-wallet')))
|
|
192
|
+
parser.add_argument('--data-dir',default=os.environ.get('KIRA_DATA_DIR',str(Path.home()/'.local/share/kira-wallet')),help='Workspace directory. Accepted before or after the subcommand.')
|
|
193
193
|
sub=parser.add_subparsers(dest='command',required=True)
|
|
194
|
-
sub.add_parser('init');s=sub.add_parser('start');s.add_argument('--port',type=int,default=8787)
|
|
194
|
+
sub.add_parser('init',help='Create or check the data directory without changing records.');s=sub.add_parser('start',help='Open the local app.');s.add_argument('--port',type=int,default=8787)
|
|
195
195
|
mode=s.add_mutually_exclusive_group()
|
|
196
196
|
mode.add_argument('--read-only',dest='controls',action='store_false',help='View recorded holdings without local browser actions.')
|
|
197
197
|
mode.add_argument('--controls',dest='controls',action='store_true',help=argparse.SUPPRESS)
|
|
198
198
|
s.set_defaults(controls=True)
|
|
199
199
|
s.add_argument('--no-open',action='store_true',help='Print the local app URL without opening a browser.')
|
|
200
|
-
setup=sub.add_parser('setup');setup.add_argument('--port',type=int,default=8787);setup.add_argument('--no-open',action='store_true')
|
|
201
|
-
sub.add_parser('stop');sub.add_parser('status');sub.add_parser('info')
|
|
202
|
-
wallets=sub.add_parser('wallets');walletsub=wallets.add_subparsers(dest='wallet_action')
|
|
200
|
+
setup=sub.add_parser('setup',help='Open the local app at first-run setup.');setup.add_argument('--port',type=int,default=8787);setup.add_argument('--no-open',action='store_true')
|
|
201
|
+
sub.add_parser('stop',help='Stop the local app.');sub.add_parser('status',help='Show whether the local app is running.');sub.add_parser('info',help='Show version, data directory, readiness and active jobs.')
|
|
202
|
+
wallets=sub.add_parser('wallets',help='List wallets or change their names.');walletsub=wallets.add_subparsers(dest='wallet_action')
|
|
203
203
|
tags=walletsub.add_parser('tags');tags.add_argument('wallet');tags.add_argument('--tag',action='append',required=True);tags.add_argument('--idempotency-key')
|
|
204
|
-
tools=sub.add_parser('tools');tools.add_argument('--allow-writes',action='store_true')
|
|
205
|
-
call=sub.add_parser('call');call.add_argument('tool');call.add_argument('--arguments',default='{}')
|
|
206
|
-
sub.add_parser('skill')
|
|
207
|
-
ui=sub.add_parser('ui');ui.add_argument('action',choices=['context'])
|
|
208
|
-
archive=sub.add_parser('archive');archive.add_argument('action',choices=['list','read','workspaces','files','export']);archive.add_argument('conversation',nargs='?');archive.add_argument('--path');archive.add_argument('--output')
|
|
209
|
-
connection=sub.add_parser('connect');connection.add_argument('provider',choices=['alchemy'])
|
|
210
|
-
d=sub.add_parser('doctor');d.add_argument('--live',action='store_true');d.add_argument('--chains',default='1,8453,81457');d.add_argument('--budget',type=int,default=30)
|
|
211
|
-
demo=sub.add_parser('demo');demo.add_argument('--port',type=int,default=8790)
|
|
212
|
-
add=sub.add_parser('add');add.add_argument('address');add.add_argument('--tag',required=True);add.add_argument('--resume')
|
|
213
|
-
refresh=sub.add_parser('refresh');refresh.add_argument('wallet');refresh.add_argument('--resume')
|
|
214
|
-
prices=sub.add_parser('prices');prices.add_argument('wallet')
|
|
204
|
+
tools=sub.add_parser('tools',help='Serve Kira tools over MCP stdio.');tools.add_argument('--allow-writes',action='store_true')
|
|
205
|
+
call=sub.add_parser('call',help='Run one Kira tool with JSON arguments.');call.add_argument('tool');call.add_argument('--arguments',default='{}')
|
|
206
|
+
sub.add_parser('skill',help='Print the agent instructions.')
|
|
207
|
+
ui=sub.add_parser('ui',help="Read the app's last recorded selection.");ui.add_argument('action',choices=['context'])
|
|
208
|
+
archive=sub.add_parser('archive',help='Read earlier conversations and generated files.');archive.add_argument('action',choices=['list','read','workspaces','files','export']);archive.add_argument('conversation',nargs='?');archive.add_argument('--path');archive.add_argument('--output')
|
|
209
|
+
connection=sub.add_parser('connect',help='Enter an Alchemy key at a hidden local prompt.');connection.add_argument('provider',choices=['alchemy'])
|
|
210
|
+
d=sub.add_parser('doctor',help='Check RPC settings, optionally with live probes.');d.add_argument('--live',action='store_true');d.add_argument('--chains',default='1,8453,81457');d.add_argument('--budget',type=int,default=30)
|
|
211
|
+
demo=sub.add_parser('demo',help='Open a sample portfolio.');demo.add_argument('--port',type=int,default=8790)
|
|
212
|
+
add=sub.add_parser('add',help='Register a public wallet and run its first holdings lookup.');add.add_argument('address');add.add_argument('--tag',required=True);add.add_argument('--resume')
|
|
213
|
+
refresh=sub.add_parser('refresh',help='Run a new holdings lookup for a wallet.');refresh.add_argument('wallet');refresh.add_argument('--resume')
|
|
214
|
+
prices=sub.add_parser('prices',help='Refresh prices without changing balances.');prices.add_argument('wallet')
|
|
215
215
|
for mutation in (add,refresh,prices):mutation.add_argument('--idempotency-key');mutation.add_argument('--enqueue',action='store_true')
|
|
216
|
-
jobs=sub.add_parser('jobs');jobsub=jobs.add_subparsers(dest='action',required=True)
|
|
216
|
+
jobs=sub.add_parser('jobs',help='List, follow and control durable jobs.');jobsub=jobs.add_subparsers(dest='action',required=True)
|
|
217
217
|
jobsub.add_parser('list');jobsub.add_parser('run')
|
|
218
218
|
wait=jobsub.add_parser('wait');wait.add_argument('job_id');wait.add_argument('--timeout',type=float,default=30)
|
|
219
219
|
for action in ('read','resume','cancel','deepen'):
|
|
220
220
|
item=jobsub.add_parser(action);item.add_argument('job_id');item.add_argument('--idempotency-key')
|
|
221
|
-
config=sub.add_parser('config');configsub=config.add_subparsers(dest='setting',required=True)
|
|
221
|
+
config=sub.add_parser('config',help='Show settings or import a key file reference.');configsub=config.add_subparsers(dest='setting',required=True)
|
|
222
222
|
configsub.add_parser('show')
|
|
223
223
|
imp=configsub.add_parser('import-env');imp.add_argument('--file',required=True);imp.add_argument('--key-env',default='ALCHEMY_API_KEY')
|
|
224
|
-
rpc=sub.add_parser('rpc');rpcsub=rpc.add_subparsers(dest='setting',required=True)
|
|
224
|
+
rpc=sub.add_parser('rpc',help='Choose public or custom RPC references.');rpcsub=rpc.add_subparsers(dest='setting',required=True)
|
|
225
225
|
rpcsub.add_parser('public')
|
|
226
226
|
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')
|
|
227
|
-
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')
|
|
227
|
+
discovery=sub.add_parser('discovery',help='Choose the token discovery provider.');discovery.add_argument('provider',choices=['none','alchemy']);discovery.add_argument('--key-env',default='ALCHEMY_API_KEY');discovery.add_argument('--explorers',action='store_true')
|
|
228
228
|
argv=sys.argv[1:]
|
|
229
|
-
#
|
|
229
|
+
# Global switches work before or after a subcommand.
|
|
230
230
|
if '--json' in argv:argv=['--json']+[a for a in argv if a!='--json']
|
|
231
|
+
for index,item in enumerate(argv):
|
|
232
|
+
if item=='--data-dir' and index+1<len(argv):argv=argv[index:index+2]+argv[:index]+argv[index+2:];break
|
|
233
|
+
if item.startswith('--data-dir='):argv=[item]+argv[:index]+argv[index+1:];break
|
|
231
234
|
args=parser.parse_args(argv);root=Path(args.data_dir).expanduser().resolve();os.environ['KIRA_DATA_DIR']=str(root)
|
|
232
235
|
if sys.version_info<(3,11):raise ValueError('Python 3.11 or newer is required.')
|
|
233
236
|
if os.name!='posix':raise ValueError('This development build requires a POSIX host. macOS is verified.')
|
package/kira_config.py
CHANGED
|
@@ -82,9 +82,10 @@ def public_status(root=None):
|
|
|
82
82
|
rpc=cfg['rpc'];discovery=cfg['discovery']
|
|
83
83
|
public=rpc['mode']=='public' or rpc['allow_public_fallback']
|
|
84
84
|
import_command=f"kira config import-env --file PATH --key-env {discovery['key_env']}"
|
|
85
|
-
|
|
85
|
+
ready=discovery['provider']=='alchemy' and available
|
|
86
|
+
return {'status':'ready' if ready else 'connection_required','rpc_mode':rpc['mode'],'read_order':('custom_first' if discovery['provider']=='alchemy' else rpc.get('priority','public_first')) if public else 'custom_only',
|
|
86
87
|
'custom_chain_count':len(rpc['chains']) if rpc['mode']=='custom' else 0,
|
|
87
|
-
'discovery_provider':discovery['provider'],'discovery_key_available':
|
|
88
|
+
'discovery_provider':discovery['provider'],'discovery_key_available':ready,
|
|
88
89
|
'key_env':discovery['key_env'],'public_discovery':False,'required_provider':'alchemy',
|
|
89
90
|
'import_command':import_command,
|
|
90
91
|
'guide':'Open Workspace settings > Alchemy. Run kira connect alchemy in a terminal and enter the app key at its hidden prompt. Return to Kira to check the connection, then refresh holdings. Actual results establish network access. Never enter or request a key in chat or the browser.',
|
package/kira_jobs.py
CHANGED
|
@@ -35,6 +35,10 @@ def signal_process_group(pgid, sig):
|
|
|
35
35
|
State = Literal['queued', 'running', 'interrupted', 'succeeded', 'partial', 'failed', 'cancelled']
|
|
36
36
|
Operation = Literal['wallet.add','wallet.refresh','prices.refresh','wallet.setTags','settings.rpc','settings.discovery','settings.provider','settings.importEnv']
|
|
37
37
|
TERMINAL = {'succeeded', 'partial', 'failed', 'cancelled', 'interrupted'}
|
|
38
|
+
# Web history uses the groups. Agents may also filter by an exact state they read on a job.
|
|
39
|
+
STATE_FILTERS = {'all': None, 'active': {'queued','running'}, 'succeeded': {'succeeded','partial'},
|
|
40
|
+
'attention': {'interrupted','failed'}, 'cancelled': {'cancelled'},
|
|
41
|
+
**{state: {state} for state in ('queued','running','partial','interrupted','failed')}}
|
|
38
42
|
ADDRESS = re.compile(r'0x[0-9a-fA-F]{40}')
|
|
39
43
|
|
|
40
44
|
class Request(TypedDict):
|
|
@@ -133,6 +137,11 @@ def fields(value, required):
|
|
|
133
137
|
if not isinstance(value, dict) or set(value) != set(required):
|
|
134
138
|
raise JobError('invalid_input', 'Unexpected or missing operation fields.')
|
|
135
139
|
|
|
140
|
+
def provider_summary(config):
|
|
141
|
+
"""Settings facts and environment names only, never values."""
|
|
142
|
+
return {'rpc_mode':config['rpc']['mode'],'discovery_provider':config['discovery']['provider'],
|
|
143
|
+
'public_fallback':config['rpc']['allow_public_fallback'],'endpoint_refs':{cid:row['url_env'] for cid,row in config['rpc']['chains'].items()}}
|
|
144
|
+
|
|
136
145
|
def identifier(value):
|
|
137
146
|
if not isinstance(value, str): raise JobError('invalid_id', 'Expected a job ID.')
|
|
138
147
|
try:
|
|
@@ -238,12 +247,10 @@ class JobStore:
|
|
|
238
247
|
def page(self, offset=0, limit=50, state='all', wallet=None):
|
|
239
248
|
from kira_state import page_bounds
|
|
240
249
|
page_bounds(offset, limit)
|
|
241
|
-
|
|
242
|
-
'attention': {'interrupted','failed'}, 'cancelled': {'cancelled'}}
|
|
243
|
-
if state not in groups:raise JobError('invalid_state','Choose a supported job state filter.')
|
|
250
|
+
if state not in STATE_FILTERS:raise JobError('invalid_state','Choose a job state filter: '+', '.join(STATE_FILTERS)+'.')
|
|
244
251
|
selected = self.wallet(wallet)['address_key'] if wallet else None
|
|
245
252
|
with self.locked():
|
|
246
|
-
rows = [r for r in self.summaries() if (
|
|
253
|
+
rows = [r for r in self.summaries() if (STATE_FILTERS[state] is None or r['state'] in STATE_FILTERS[state])
|
|
247
254
|
and (selected is None or (r['input'].get('wallet') or r['input'].get('address','')).lower() == selected)]
|
|
248
255
|
rows.sort(key=lambda r: r['created_at'], reverse=True)
|
|
249
256
|
jobs = [self.get(r['job_id']) for r in rows[offset:offset+limit]]
|
|
@@ -545,7 +552,13 @@ class JobStore:
|
|
|
545
552
|
paths = {w['latest_snapshot']['directory'] for w in read(self.root / 'wallets.json')['wallets'] if w['address_key'] == key and w.get('latest_snapshot')}
|
|
546
553
|
for run in read(self.root / 'wallets.json').get('research_runs', []):
|
|
547
554
|
if run['address_key'] == key: paths.add(run['snapshot'])
|
|
548
|
-
|
|
555
|
+
rows = []
|
|
556
|
+
for path in paths:
|
|
557
|
+
# phase "initial" marks progress published while a holdings lookup was still running.
|
|
558
|
+
snapshot = self.snapshot(path)
|
|
559
|
+
rows.append({'snapshot_id': path, 'compiled_at': snapshot.get('compiled_at'), 'status': snapshot.get('status'),
|
|
560
|
+
'phase': (snapshot.get('pipeline') or {}).get('phase')})
|
|
561
|
+
return sorted(rows, key=lambda r: r['compiled_at'] or '')
|
|
549
562
|
|
|
550
563
|
def publication(self, job):
|
|
551
564
|
if job['operation'].startswith('settings.') or job['operation']=='wallet.setTags':
|
|
@@ -568,7 +581,8 @@ class JobStore:
|
|
|
568
581
|
previous = read(projection) if projection.exists() else {}
|
|
569
582
|
value = read(overlay)
|
|
570
583
|
if (previous.get('observed_at') or '') <= value['observed_at']: atomic(projection, value)
|
|
571
|
-
return {'snapshot_id': item['snapshot_id'], 'overlay': item['overlay'], 'status': item['status']
|
|
584
|
+
return {'snapshot_id': item['snapshot_id'], 'overlay': item['overlay'], 'status': item['status'],
|
|
585
|
+
**({'gaps': value['job_result_gaps']} if isinstance(value.get('job_result_gaps'), list) else {})}
|
|
572
586
|
manifest = folder / 'run.json'
|
|
573
587
|
if not manifest.exists() or not (folder / 'results.json').exists(): return None
|
|
574
588
|
result = read(folder / 'results.json')
|
|
@@ -710,7 +724,8 @@ class JobStore:
|
|
|
710
724
|
if job['input']['provider']=='alchemy' and not secret_values(config).get(job['input']['key_env']):
|
|
711
725
|
raise JobError('connection_key_missing','Kira cannot find the local Alchemy key. Follow the connection guide, then retry. Existing settings are unchanged.')
|
|
712
726
|
atomic(config_path(),config)
|
|
713
|
-
result={'status':'completed','setting':job['operation']
|
|
727
|
+
result={'status':'completed','setting':job['operation'],'provider_configuration':provider_summary(config)}
|
|
728
|
+
atomic(self.jobs/'results'/(job['job_id']+'.json'),result);return result
|
|
714
729
|
self.require_connection()
|
|
715
730
|
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', 'KIRA_RESEARCH_GENERATION':job.get('parent_job_id',job['job_id'])}
|
|
716
731
|
command=self.engine_command(job)
|
|
@@ -815,10 +830,10 @@ class JobStore:
|
|
|
815
830
|
if entry.get('research_generation') not in (None,job.get('parent_job_id')):
|
|
816
831
|
job.update(state='cancelled',stage='stopped',errors=[{'code':'superseded','message':'Newer holdings research replaced this detailed run. Earlier evidence is preserved.'}]);self.save(job);writer.close();continue
|
|
817
832
|
if job:
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
833
|
+
# Research records the settings it ran with. Settings jobs report their result instead.
|
|
834
|
+
if not job['operation'].startswith('settings.'):
|
|
835
|
+
from kira_config import load_config
|
|
836
|
+
job['provider_configuration']=provider_summary(load_config())
|
|
822
837
|
job.update(state='running', stage='starting', started_at=now(), attempt=job['attempt'] + 1, errors=[]); self.save(job)
|
|
823
838
|
if not job:
|
|
824
839
|
time.sleep(.5)
|
package/kira_records.py
CHANGED
|
@@ -209,7 +209,7 @@ def projection(root, config, *, token_id=None, limit=12, offset=0, wallet_key=No
|
|
|
209
209
|
def compact_wallet_facts(rows):
|
|
210
210
|
asset_fields=('id','chain_id','address','display_identity','symbol','name','environment','balance','is_native','value_usd','historical_value_usd','balance_observed_at','price','pools','pools_recorded','pools_omitted','exit_quote_recorded','archived')
|
|
211
211
|
return [{**row,'assets':[{key:asset[key] for key in asset_fields if key in asset and
|
|
212
|
-
(asset[key] is not None or key in ('value_usd','price'))}
|
|
212
|
+
(asset[key] is not None or key in ('address','value_usd','price'))}
|
|
213
213
|
for asset in row['assets']]} for row in rows]
|
|
214
214
|
|
|
215
215
|
def recorded_valuation(assets):
|
package/kira_service.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import time
|
|
6
6
|
import uuid
|
|
7
|
-
from kira_jobs import JobStore, JobError, atomic, fields
|
|
7
|
+
from kira_jobs import JobStore, JobError, atomic, fields, STATE_FILTERS
|
|
8
8
|
from kira_state import StateCache, page, page_bounds
|
|
9
9
|
from kira_research_tools import ResearchTools, CATALOG
|
|
10
10
|
|
|
@@ -21,8 +21,12 @@ for row in CATALOG:
|
|
|
21
21
|
properties[name]={**schema,'description':description}
|
|
22
22
|
if row['name'] in ('portfolio_read','job_list','snapshots_list'):
|
|
23
23
|
properties.update(offset=INTEGER,limit={'type':'integer','minimum':1,'maximum':100})
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
summary=row['description']
|
|
25
|
+
if row['name']=='job_list':
|
|
26
|
+
summary='Read a newest-first page of durable jobs. Optionally filter by state or registered wallet.'
|
|
27
|
+
properties.update(state={'type':'string','enum':list(STATE_FILTERS),'description':'Default all. Groups: active (queued, running), succeeded (succeeded, partial), attention (interrupted, failed). The other values match one exact state.'},
|
|
28
|
+
wallet={**STRING,'description':'registered address or exact name'})
|
|
29
|
+
READ_TOOLS.append({'name':row['name'],'description':summary,
|
|
26
30
|
'inputSchema':{'type':'object','properties':properties,'required':list(row['arguments']),'additionalProperties':False},
|
|
27
31
|
'annotations':{'readOnlyHint':True,'destructiveHint':False,'idempotentHint':True,'openWorldHint':False}})
|
|
28
32
|
|
|
@@ -63,6 +67,8 @@ def validate(arguments,schema):
|
|
|
63
67
|
field=schema['properties'][name];types=field['type'] if isinstance(field['type'],list) else [field['type']]
|
|
64
68
|
if not any((type(value) in kinds[k] if isinstance(kinds[k],tuple) else type(value) is kinds[k]) for k in types):
|
|
65
69
|
raise JobError('invalid_input','Invalid argument type: '+name)
|
|
70
|
+
if 'enum' in field and value not in field['enum']:
|
|
71
|
+
raise JobError('invalid_input','Choose '+name+' from: '+', '.join(map(str,field['enum']))+'.')
|
|
66
72
|
if isinstance(value,str) and (len(value)>field.get('maxLength',500) or len(value)<field.get('minLength',0)):
|
|
67
73
|
raise JobError('invalid_input','Use a bounded identifier: '+name)
|
|
68
74
|
if type(value) in (int,float) and (value<field.get('minimum',value) or value>field.get('maximum',value)):
|
package/package.json
CHANGED
|
@@ -12,8 +12,9 @@ and MCP operations.
|
|
|
12
12
|
|
|
13
13
|
## Start with the existing workspace
|
|
14
14
|
|
|
15
|
-
1. Run `kira --version`, then `kira info --json` and inspect
|
|
16
|
-
`
|
|
15
|
+
1. Run `kira --version`, then `kira info --json` and inspect `data_dir` and
|
|
16
|
+
`research_connection.status`: `ready` means Alchemy is connected;
|
|
17
|
+
`connection_required` means new research cannot start yet. The commands below require 0.2.0 or later. If installation is
|
|
17
18
|
part of the request, use `npm install -g kira-wallet` or the supplied
|
|
18
19
|
prepared package archive. Requirements are Node.js 22+, Python 3.11+ and
|
|
19
20
|
POSIX; macOS is verified.
|
|
@@ -44,8 +45,12 @@ kira config show --json
|
|
|
44
45
|
|
|
45
46
|
An existing private environment file can be referenced with
|
|
46
47
|
`kira config import-env --file /absolute/private/rpc.env --key-env ALCHEMY_API_KEY --json`.
|
|
47
|
-
Use the human-supplied path and name. This does not copy keys.
|
|
48
|
-
|
|
48
|
+
Use the human-supplied path and name. This does not copy keys. It also selects
|
|
49
|
+
Alchemy for token discovery and network reads, so a separate
|
|
50
|
+
`kira discovery alchemy` step is not needed. Configuration commands with
|
|
51
|
+
`--json` return jobs; check completion before claiming readiness. A finished
|
|
52
|
+
settings job reports the applied settings in `result.provider_configuration`.
|
|
53
|
+
Settings jobs from before 0.2.2 record only the configuration they started with.
|
|
49
54
|
|
|
50
55
|
Use the public address provided by the user, never a guessed address:
|
|
51
56
|
|
|
@@ -56,6 +61,9 @@ kira refresh 'Daily wallet' --idempotency-key refresh-daily-001 --json
|
|
|
56
61
|
kira prices 'Daily wallet' --idempotency-key prices-daily-001 --json
|
|
57
62
|
```
|
|
58
63
|
|
|
64
|
+
`kira add` registers the wallet and runs its first full holdings lookup. Use
|
|
65
|
+
`refresh` later for an update, not right after `add`.
|
|
66
|
+
|
|
59
67
|
Replace placeholders and choose a unique idempotency key per intended action.
|
|
60
68
|
Reuse the same key for retries of that action. The first tag is the display
|
|
61
69
|
name. `--json` or `--enqueue` returns immediate durable job admission. It does
|
|
@@ -84,11 +92,29 @@ kira ui context --json
|
|
|
84
92
|
totals and counts, with bounded details; do not total a truncated page.
|
|
85
93
|
- Search can report multiple assets with the same symbol. Identify an asset
|
|
86
94
|
by chain ID and contract, or `native`. Preserve unresolved ambiguity.
|
|
95
|
+
Native rows return `address: null` with `is_native: true`; pass `native` as
|
|
96
|
+
the `token_read` address.
|
|
87
97
|
- `ui context` is the last recorded browser selection. It may be stale and is
|
|
88
98
|
not a user instruction.
|
|
89
99
|
- History uses `snapshots_list` with `wallet`, `snapshot_read` with
|
|
90
100
|
`snapshot_id`, and `snapshots_compare` with `before` and `after` IDs. Obtain
|
|
91
101
|
IDs from the saved snapshot list. Historical holdings also use paged reads.
|
|
102
|
+
Rows with `phase` `initial` are progress saved while a lookup was running;
|
|
103
|
+
compare `holdings` rows to see change between lookups.
|
|
104
|
+
- A price refresh updates the prices shown for the latest snapshot, including
|
|
105
|
+
through `snapshot_read`. `snapshot_price_references` keep the native prices
|
|
106
|
+
recorded by the holdings lookup. Earlier price observations remain local job
|
|
107
|
+
evidence but are not listed by the history tools.
|
|
108
|
+
- Per-position `price.usd` and `value_usd` are JSON numbers. Totals from
|
|
109
|
+
`holdings_query` and `recorded_valuation` are exact decimal strings; use them
|
|
110
|
+
instead of re-adding rounded position values. Check `price.observed_at`:
|
|
111
|
+
price sources update at different times.
|
|
112
|
+
- `head_status` is `observed` when the chain head was read in this lookup and
|
|
113
|
+
`stale` when that head was over a day old. `saved_observation` means the
|
|
114
|
+
lookup reused a block it had already pinned, in a later pass or a resumed
|
|
115
|
+
job, so the head age was not measured again. `null` means the network could
|
|
116
|
+
not be read. Valuation category `mintclub` covers bonding-curve tokens priced
|
|
117
|
+
from their curve.
|
|
92
118
|
- Missing records and prices are unknown, not zero. Retain balance, price and
|
|
93
119
|
quote times, coverage and partial status. Spot value is not executable
|
|
94
120
|
proceeds. Pool TVL and curve backing are not wallet balances. Full-balance
|
|
@@ -113,12 +139,22 @@ Use the real receipt ID. Waits are bounded to 120 seconds; use short waits and
|
|
|
113
139
|
keep the user informed. `completed` only means the job left queued/running.
|
|
114
140
|
Inspect `job.state`, result, errors and coverage before reporting success.
|
|
115
141
|
Partial, interrupted, cancelled and failed states have different meanings.
|
|
142
|
+
Progress events count work per `context.operation`, such as `registry` or
|
|
143
|
+
`balances`. Balance totals grow as the registry scan finds candidates, so do
|
|
144
|
+
not derive one overall percentage. A job's `chains` entries report network
|
|
145
|
+
reads; use `wallet_read` or `network_read` coverage for discovery gaps. A
|
|
146
|
+
partial price job names its reasons in `result.gaps`. Job records keep the
|
|
147
|
+
latest 100 events.
|
|
116
148
|
Accepted work survives viewer shutdown or agent disconnection. Cancel only
|
|
117
149
|
when requested; resume interrupted jobs explicitly. `jobs deepen` adds
|
|
118
150
|
optional sell-route analysis from saved holdings without rediscovering tokens.
|
|
119
151
|
|
|
120
152
|
Use `call job_list` with optional `state`, `wallet`, `offset` and `limit` for
|
|
121
|
-
paged job discovery. `
|
|
153
|
+
paged job discovery. `state` is `all` (default), a group (`active`,
|
|
154
|
+
`succeeded`, `attention`, `cancelled`) or an exact state (`queued`, `running`,
|
|
155
|
+
`partial`, `interrupted`, `failed`). `succeeded` includes partial results;
|
|
156
|
+
check `job.state` to separate them.
|
|
157
|
+
`kira jobs run` starts queued work, not interrupted jobs.
|
|
122
158
|
|
|
123
159
|
## Shared tools and earlier archives
|
|
124
160
|
|
package/viewer/refresh_prices.py
CHANGED
|
@@ -162,7 +162,15 @@ for w in selected:
|
|
|
162
162
|
overlay=job_folder/'prices.json'
|
|
163
163
|
output['snapshot_id']=w['latest_snapshot']['directory']
|
|
164
164
|
missing_native=any(c.get('environment')=='mainnet' and float(c.get('native_balance') or 0)>0 and c.get('native_symbol')+'_USD' not in fresh_native for c in snapshot.get('coverage',[]))
|
|
165
|
-
|
|
165
|
+
# Name each gap so a partial price job explains itself.
|
|
166
|
+
output['job_result_gaps']=[name for name,found in (
|
|
167
|
+
('market_discovery_partial',output.get('market_discovery_status')=='partial'),
|
|
168
|
+
('native_price_missing',missing_native),
|
|
169
|
+
('earlier_prices_retained',bool(retained)),
|
|
170
|
+
('reserve_pricing_partial',output.get('reserve_price_status')=='partial' or bool((output.get('reserve_price_graph') or {}).get('errors'))),
|
|
171
|
+
('curve_reads_incomplete',bool(rpc.get('errors')) or any('error' in p for p in rpc['tokens'])),
|
|
172
|
+
('provider_request_failed',any(isinstance(r['response'],dict) and 'transport_error' in r['response'] for r in fetched))) if found]
|
|
173
|
+
output['job_result_status']='completed_with_coverage_gaps' if output['job_result_gaps'] else 'completed'
|
|
166
174
|
atomic(overlay,output)
|
|
167
175
|
atomic(job_folder/'price-publication.json',{'schema_version':1,'snapshot_id':w['latest_snapshot']['directory'],
|
|
168
176
|
'overlay':str(overlay.relative_to(h.ROOT)),'projection':str(destination.relative_to(h.ROOT)),
|