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 +2 -2
- package/src/commands/init.ts +6 -74
- package/src/commands/login.ts +16 -0
- package/src/config/setup.ts +83 -0
- package/src/index.ts +4 -1
- package/src/util/capture-shim.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lyra-ai-agent",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
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",
|
package/src/commands/init.ts
CHANGED
|
@@ -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
|
|
15
|
-
import {
|
|
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
|
|
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
|
-
}
|
package/src/commands/login.ts
CHANGED
|
@@ -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:',
|
|
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 {}
|