lyra-ai-agent 0.1.3 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lyra-ai-agent",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "lyra CLI: a Sui-native, policy-bound AI finance agent. Real on-chain work gated by deterministic policy, simulation, and approval",
6
6
  "license": "MIT",
@@ -52,7 +52,7 @@
52
52
  "@opentui/solid": "^0.1.97",
53
53
  "lyra-core": "^0.1.2",
54
54
  "lyra-gateway": "^0.1.2",
55
- "lyra-plugin-onchain": "^0.1.2",
55
+ "lyra-plugin-onchain": "^0.1.5",
56
56
  "lyra-plugin-system": "^0.1.2",
57
57
  "lyra-plugin-telegram": "^0.1.2",
58
58
  "picocolors": "^1.1.1",
@@ -1,18 +1,10 @@
1
1
  import { existsSync } from 'node:fs'
2
- import { mkdir, writeFile } from 'node:fs/promises'
3
- import { join } from 'node:path'
4
2
  import { cancel, intro, isCancel, outro, password, select } from '@clack/prompts'
5
3
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
6
- import {
7
- type LyraNetwork,
8
- agentPaths,
9
- defineConfig,
10
- placeholderAgentId,
11
- suiRpcUrl,
12
- } from 'lyra-core'
4
+ import { type LyraNetwork, agentPaths, suiRpcUrl } from 'lyra-core'
13
5
  import { keypairFromSecret } from 'lyra-plugin-onchain'
14
- import { DEFAULT_NETWORK, resolvePackageId } from '../config/defaults'
15
- import { writeConfigTs } from '../config/render'
6
+ import { DEFAULT_NETWORK } from '../config/defaults'
7
+ import { finalizeSetup } from '../config/setup'
16
8
  import { setDotenvVar } from '../util/dotenv'
17
9
  import { type SuiAgent, loadAgent, writeAgentKey } from '../util/sui-runtime'
18
10
  import { pickBrainModel } from './init/model-picker'
@@ -156,43 +148,15 @@ export async function runInit(opts?: InitOpts): Promise<void> {
156
148
  }
157
149
 
158
150
  const modelPick = await pickBrainModel()
159
-
160
- const agentId = placeholderAgentId(agent.address)
161
- const paths = agentPaths.agent(agentId)
162
- await mkdir(paths.dir, { recursive: true })
163
-
164
- // Telegram is env-driven on Sui (TELEGRAM_BOT_TOKEN). Auto-enable the plugin
165
- // when the token is present so `lyra` brings the DM gateway online.
166
151
  const telegramEnabled = !!process.env.TELEGRAM_BOT_TOKEN
167
152
 
168
- await seedStarterMemoryFiles({
169
- paths,
170
- network,
153
+ const { agentId, packageId } = await finalizeSetup({
171
154
  agentAddress: agent.address,
155
+ linkedOwner,
156
+ network,
172
157
  brainProvider: modelPick?.provider ?? null,
173
158
  brainModel: modelPick?.model ?? null,
174
159
  })
175
-
176
- const cfg = defineConfig({
177
- identity: {
178
- operator: linkedOwner,
179
- agent: agent.address,
180
- },
181
- network,
182
- storage: { network },
183
- brain: {
184
- provider: modelPick?.provider ?? null,
185
- model: modelPick?.model ?? null,
186
- },
187
- plugins: telegramEnabled ? ['onchain', 'system', 'telegram'] : ['onchain', 'system'],
188
- tools: {},
189
- imports: { claudeCode: true },
190
- })
191
- await writeConfigTs(configPath, cfg, {
192
- header: '// Regenerated by `lyra init`. Edit freely; type-safe.',
193
- })
194
-
195
- const packageId = resolvePackageId()
196
160
  const lines = [
197
161
  '',
198
162
  ` agent id ${agentId}`,
@@ -211,35 +175,3 @@ export async function runInit(opts?: InitOpts): Promise<void> {
211
175
  )
212
176
  outro(lines.join('\n'))
213
177
  }
214
-
215
- interface SeedStarterOpts {
216
- paths: ReturnType<typeof agentPaths.agent>
217
- network: LyraNetwork
218
- agentAddress: string
219
- brainProvider: string | null
220
- brainModel: string | null
221
- }
222
-
223
- /**
224
- * Seed `MEMORY.md`, `/agent/identity.md`, and `/agent/persona.md` so the
225
- * brain's first turn sees a parseable memory index and introduces itself.
226
- */
227
- async function seedStarterMemoryFiles(opts: SeedStarterOpts): Promise<void> {
228
- const memDir = opts.paths.memoryDir
229
- const agentMem = `${memDir}/agent`
230
- const userMem = `${memDir}/user`
231
- await mkdir(agentMem, { recursive: true })
232
- await mkdir(userMem, { recursive: true })
233
-
234
- const now = new Date().toISOString().slice(0, 10)
235
- const identity = `---\nname: identity\ndescription: Auto-written agent identity facts.\ntype: agent-identity\n---\n# Lyra identity\n\n- Name: Lyra\n- Agent address: ${opts.agentAddress} (${opts.network})\n- Created: ${now}\n${opts.brainProvider ? `- Brain provider: ${opts.brainProvider}\n` : ''}${opts.brainModel ? `- Brain model: ${opts.brainModel}\n` : ''}`
236
- const persona =
237
- '---\nname: persona\ndescription: Voice + behavior style.\ntype: agent-persona\n---\n# Persona\n\nI am Lyra, a Sui-native autonomous finance agent. I convert goals into policy-checked PTBs, execute only within my approved protocol scope, and store auditable memory and receipts with Walrus. Every value-moving action is checked by a deterministic policy (mirrored on-chain by lyra::policy) before it runs. I am direct, concise, and factual.\n'
238
- const profile =
239
- '---\nname: profile\ndescription: User profile (local only).\ntype: user\n---\n# User profile\n\n(empty, fills as we chat)\n'
240
-
241
- await writeFile(join(agentMem, 'identity.md'), identity, 'utf8')
242
- await writeFile(join(agentMem, 'persona.md'), persona, 'utf8')
243
- await writeFile(join(userMem, 'profile.md'), profile, 'utf8')
244
- await writeFile(opts.paths.memoryIndex, '# Lyra Memory Index\n\n', 'utf8')
245
- }
@@ -14,7 +14,11 @@
14
14
  * derives for that owner — so the CLI and the web operate one identical agent.
15
15
  */
16
16
 
17
+ import { existsSync } from 'node:fs'
18
+ import { agentPaths } from 'lyra-core'
17
19
  import { keypairFromSecret } from 'lyra-plugin-onchain'
20
+ import { DEFAULT_NETWORK } from '../config/defaults'
21
+ import { finalizeSetup } from '../config/setup'
18
22
  import { writeAgentKey } from '../util/sui-runtime'
19
23
 
20
24
  /** Default web origin; override with LYRA_WEB_URL for local/staging testing. */
@@ -150,6 +154,18 @@ export async function runLogin(): Promise<void> {
150
154
  console.log(`✓ Linked agent ${result.address} (same as your web wallet)`)
151
155
  console.log(` owner ${result.owner}`)
152
156
  console.log(` key ${result.keyPath}`)
157
+
158
+ // Login used to write ONLY the agent key, leaving `lyra` (chat) with no config
159
+ // to load. Write a runnable config (defaults) when one doesn't exist yet so the
160
+ // device-link is a complete setup; never clobber an existing config.
161
+ if (!existsSync(agentPaths.config)) {
162
+ await finalizeSetup({
163
+ agentAddress: result.address,
164
+ linkedOwner: result.owner,
165
+ network: DEFAULT_NETWORK,
166
+ })
167
+ console.log(` config ${agentPaths.config}`)
168
+ }
153
169
  console.log('')
154
170
  console.log('Next: `lyra` to chat · `lyra status` for health')
155
171
  } catch (e) {
@@ -0,0 +1,83 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import { type LyraNetwork, agentPaths, defineConfig, placeholderAgentId } from 'lyra-core'
4
+ import { resolvePackageId } from './defaults'
5
+ import { writeConfigTs } from './render'
6
+
7
+ /**
8
+ * Write `~/.lyra/config.ts` + seed starter memory for an agent we already have
9
+ * (a fresh key, or one linked from the web). Shared by `lyra init` and
10
+ * `lyra login` so BOTH produce a complete, runnable setup — `lyra login` used to
11
+ * write only the agent key, leaving `lyra` (chat) without a config to load.
12
+ */
13
+ export async function finalizeSetup(opts: {
14
+ agentAddress: string
15
+ linkedOwner: string | null
16
+ network: LyraNetwork
17
+ brainProvider?: string | null
18
+ brainModel?: string | null
19
+ }): Promise<{ agentId: string; configPath: string; packageId: string }> {
20
+ const configPath = agentPaths.config
21
+ const agentId = placeholderAgentId(opts.agentAddress)
22
+ const paths = agentPaths.agent(agentId)
23
+ await mkdir(paths.dir, { recursive: true })
24
+
25
+ // Telegram is env-driven on Sui (TELEGRAM_BOT_TOKEN). Auto-enable the plugin
26
+ // when the token is present so `lyra` brings the DM gateway online.
27
+ const telegramEnabled = !!process.env.TELEGRAM_BOT_TOKEN
28
+
29
+ await seedStarterMemoryFiles({
30
+ paths,
31
+ network: opts.network,
32
+ agentAddress: opts.agentAddress,
33
+ brainProvider: opts.brainProvider ?? null,
34
+ brainModel: opts.brainModel ?? null,
35
+ })
36
+
37
+ const cfg = defineConfig({
38
+ identity: { operator: opts.linkedOwner, agent: opts.agentAddress },
39
+ network: opts.network,
40
+ storage: { network: opts.network },
41
+ brain: { provider: opts.brainProvider ?? null, model: opts.brainModel ?? null },
42
+ plugins: telegramEnabled ? ['onchain', 'system', 'telegram'] : ['onchain', 'system'],
43
+ tools: {},
44
+ imports: { claudeCode: true },
45
+ })
46
+ await writeConfigTs(configPath, cfg, {
47
+ header: '// Written by lyra (init / login). Edit freely; type-safe.',
48
+ })
49
+
50
+ return { agentId, configPath, packageId: resolvePackageId() }
51
+ }
52
+
53
+ interface SeedStarterOpts {
54
+ paths: ReturnType<typeof agentPaths.agent>
55
+ network: LyraNetwork
56
+ agentAddress: string
57
+ brainProvider: string | null
58
+ brainModel: string | null
59
+ }
60
+
61
+ /**
62
+ * Seed `MEMORY.md`, `/agent/identity.md`, and `/agent/persona.md` so the brain's
63
+ * first turn sees a parseable memory index and introduces itself.
64
+ */
65
+ export async function seedStarterMemoryFiles(opts: SeedStarterOpts): Promise<void> {
66
+ const memDir = opts.paths.memoryDir
67
+ const agentMem = `${memDir}/agent`
68
+ const userMem = `${memDir}/user`
69
+ await mkdir(agentMem, { recursive: true })
70
+ await mkdir(userMem, { recursive: true })
71
+
72
+ const now = new Date().toISOString().slice(0, 10)
73
+ const identity = `---\nname: identity\ndescription: Auto-written agent identity facts.\ntype: agent-identity\n---\n# Lyra identity\n\n- Name: Lyra\n- Agent address: ${opts.agentAddress} (${opts.network})\n- Created: ${now}\n${opts.brainProvider ? `- Brain provider: ${opts.brainProvider}\n` : ''}${opts.brainModel ? `- Brain model: ${opts.brainModel}\n` : ''}`
74
+ const persona =
75
+ '---\nname: persona\ndescription: Voice + behavior style.\ntype: agent-persona\n---\n# Persona\n\nI am Lyra, a Sui-native autonomous finance agent. I convert goals into policy-checked PTBs, execute only within my approved protocol scope, and store auditable memory and receipts with Walrus. Every value-moving action is checked by a deterministic policy (mirrored on-chain by lyra::policy) before it runs. I am direct, concise, and factual.\n'
76
+ const profile =
77
+ '---\nname: profile\ndescription: User profile (local only).\ntype: user\n---\n# User profile\n\n(empty, fills as we chat)\n'
78
+
79
+ await writeFile(join(agentMem, 'identity.md'), identity, 'utf8')
80
+ await writeFile(join(agentMem, 'persona.md'), persona, 'utf8')
81
+ await writeFile(join(userMem, 'profile.md'), profile, 'utf8')
82
+ await writeFile(opts.paths.memoryIndex, '# Lyra Memory Index\n\n', 'utf8')
83
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,9 @@
3
3
  * commands/<name>.
4
4
  */
5
5
 
6
+ // MUST be first: guards Error.captureStackTrace so navi-sdk's transitive
7
+ // follow-redirects import doesn't crash Bun at startup. See the shim for why.
8
+ import './util/capture-shim'
6
9
  import { loadDotenvFile } from './util/dotenv'
7
10
 
8
11
  // Zero-env-var startup: load `~/.lyra/.env` (e.g. the OPENAI_API_KEY that
@@ -160,6 +163,6 @@ main()
160
163
  process.exit(0)
161
164
  })
162
165
  .catch(e => {
163
- console.error('fatal:', (e as Error).message)
166
+ console.error('fatal:', e instanceof Error ? e.message : e)
164
167
  process.exit(1)
165
168
  })
@@ -0,0 +1,21 @@
1
+ // Bun's `Error.captureStackTrace` throws "First argument must be an Error object"
2
+ // for some calls `follow-redirects` makes at module-init time (it builds error
3
+ // subclasses via `new BaseError()` whose constructor calls captureStackTrace).
4
+ // follow-redirects is pulled in by axios → navi-sdk, so importing the on-chain
5
+ // plugin (lending tools) used to crash `lyra` chat on startup.
6
+ //
7
+ // Guard captureStackTrace so such calls are a harmless no-op instead of throwing
8
+ // — the affected error objects simply won't carry a V8-captured stack. This MUST
9
+ // be imported before anything that transitively loads navi-sdk/axios.
10
+ const original = Error.captureStackTrace
11
+ if (typeof original === 'function') {
12
+ Error.captureStackTrace = ((target: object, ctor?: unknown) => {
13
+ try {
14
+ ;(original as (t: object, c?: unknown) => void).call(Error, target, ctor)
15
+ } catch {
16
+ // Bun rejects some non-Error targets here — ignore.
17
+ }
18
+ }) as typeof Error.captureStackTrace
19
+ }
20
+
21
+ export {}