nebula-ai-agent 0.3.1
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 +39 -0
- package/bin/nebula +11 -0
- package/package.json +50 -0
- package/src/commands/_agents.ts +14 -0
- package/src/commands/_unlock.ts +66 -0
- package/src/commands/agent-wallet.ts +90 -0
- package/src/commands/chat-telegram.ts +398 -0
- package/src/commands/chat.tsx +1308 -0
- package/src/commands/drain.ts +90 -0
- package/src/commands/gateway-logs.ts +49 -0
- package/src/commands/gateway-run.ts +42 -0
- package/src/commands/gateway-start.ts +216 -0
- package/src/commands/gateway-status.ts +90 -0
- package/src/commands/gateway-stop.ts +133 -0
- package/src/commands/gateway.ts +101 -0
- package/src/commands/identity.ts +178 -0
- package/src/commands/init/cost.ts +40 -0
- package/src/commands/init/funding-gate.ts +64 -0
- package/src/commands/init/model-picker.ts +25 -0
- package/src/commands/init/operator-picker.ts +233 -0
- package/src/commands/init/telegram-step.ts +245 -0
- package/src/commands/init/wizard-state.ts +94 -0
- package/src/commands/init.ts +439 -0
- package/src/commands/login.ts +86 -0
- package/src/commands/logs.ts +37 -0
- package/src/commands/model.ts +48 -0
- package/src/commands/pairing-approve.ts +65 -0
- package/src/commands/pairing-clear.ts +39 -0
- package/src/commands/pairing-list.ts +55 -0
- package/src/commands/pairing-revoke.ts +49 -0
- package/src/commands/pairing.ts +81 -0
- package/src/commands/status.ts +44 -0
- package/src/commands/telegram-remove.ts +62 -0
- package/src/commands/telegram-setup.ts +64 -0
- package/src/commands/telegram-status.ts +87 -0
- package/src/commands/telegram.ts +44 -0
- package/src/commands/trust.ts +196 -0
- package/src/config/load.ts +35 -0
- package/src/config/render.ts +99 -0
- package/src/index.ts +184 -0
- package/src/profile/crypto.ts +68 -0
- package/src/profile/derive.ts +25 -0
- package/src/profile/store.ts +86 -0
- package/src/profile/unlock.ts +29 -0
- package/src/ui/app.tsx +719 -0
- package/src/ui/approval-summary.ts +32 -0
- package/src/ui/markdown-parse.ts +219 -0
- package/src/ui/markdown.tsx +37 -0
- package/src/ui/state.ts +181 -0
- package/src/util/bootstrap-mode.ts +25 -0
- package/src/util/bootstrap-progress-box.ts +378 -0
- package/src/util/cli-version.ts +28 -0
- package/src/util/format.ts +11 -0
- package/src/util/gateway-spawn.ts +125 -0
- package/src/util/gateway-version.ts +154 -0
- package/src/util/github-releases.ts +79 -0
- package/src/util/profile-key.ts +25 -0
- package/src/util/ref-resolver.ts +55 -0
- package/src/util/silence-console.ts +40 -0
- package/src/util/telegram-secrets.ts +218 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-disk profile + session for password login.
|
|
3
|
+
*
|
|
4
|
+
* - profile.json : the agent key encrypted at rest under the password (scrypt +
|
|
5
|
+
* AES-256-GCM). Never holds a plaintext key.
|
|
6
|
+
* - session.json : a short-lived unlock so commands don't re-prompt every time.
|
|
7
|
+
* Holds the decrypted key with `0600` perms and an expiry — the convenience/
|
|
8
|
+
* security trade-off of a password profile. `nebula logout` clears it.
|
|
9
|
+
*
|
|
10
|
+
* Both live under `~/.nebula` (overridable via NEBULA_ROOT).
|
|
11
|
+
*/
|
|
12
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
13
|
+
import { homedir } from 'node:os'
|
|
14
|
+
import { join } from 'node:path'
|
|
15
|
+
import type { ProfileCipher } from './crypto'
|
|
16
|
+
|
|
17
|
+
function root(): string {
|
|
18
|
+
return process.env.NEBULA_ROOT ?? join(homedir(), '.nebula')
|
|
19
|
+
}
|
|
20
|
+
export function profilePath(): string {
|
|
21
|
+
return join(root(), 'profile.json')
|
|
22
|
+
}
|
|
23
|
+
export function sessionPath(): string {
|
|
24
|
+
return join(root(), 'session.json')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProfileFile {
|
|
28
|
+
v: 1
|
|
29
|
+
address: string
|
|
30
|
+
cipher: ProfileCipher
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SessionFile {
|
|
34
|
+
address: string
|
|
35
|
+
privkey: string
|
|
36
|
+
expiresAt: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const DEFAULT_SESSION_TTL_MS = 12 * 60 * 60 * 1000 // 12h
|
|
40
|
+
|
|
41
|
+
async function readJson<T>(path: string): Promise<T | null> {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(await readFile(path, 'utf8')) as T
|
|
44
|
+
} catch {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function readProfile(): Promise<ProfileFile | null> {
|
|
50
|
+
const p = await readJson<ProfileFile>(profilePath())
|
|
51
|
+
return p?.address && p?.cipher ? p : null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function hasProfile(): Promise<boolean> {
|
|
55
|
+
return (await readProfile()) !== null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function writeProfile(address: string, cipher: ProfileCipher): Promise<void> {
|
|
59
|
+
await mkdir(root(), { recursive: true })
|
|
60
|
+
const body: ProfileFile = { v: 1, address, cipher }
|
|
61
|
+
await writeFile(profilePath(), JSON.stringify(body, null, 2), { mode: 0o600 })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A non-expired session whose address matches `address` (if given), else null. */
|
|
65
|
+
export async function readSession(now: number, address?: string): Promise<SessionFile | null> {
|
|
66
|
+
const s = await readJson<SessionFile>(sessionPath())
|
|
67
|
+
if (!s || !s.privkey || !s.address) return null
|
|
68
|
+
if (typeof s.expiresAt !== 'number' || s.expiresAt <= now) return null
|
|
69
|
+
if (address && s.address.toLowerCase() !== address.toLowerCase()) return null
|
|
70
|
+
return s
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function writeSession(
|
|
74
|
+
address: string,
|
|
75
|
+
privkey: string,
|
|
76
|
+
now: number,
|
|
77
|
+
ttlMs: number = DEFAULT_SESSION_TTL_MS,
|
|
78
|
+
): Promise<void> {
|
|
79
|
+
await mkdir(root(), { recursive: true })
|
|
80
|
+
const body: SessionFile = { address, privkey, expiresAt: now + ttlMs }
|
|
81
|
+
await writeFile(sessionPath(), JSON.stringify(body), { mode: 0o600 })
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function clearSession(): Promise<void> {
|
|
85
|
+
await rm(sessionPath(), { force: true })
|
|
86
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fast unlock via the password profile, for commands that need the agent key.
|
|
3
|
+
* Returns the agent private key from a live session, or by prompting for the
|
|
4
|
+
* profile password (refreshing the session), or null to fall back to the
|
|
5
|
+
* operator-signature unlock. Never throws — a wrong password returns null.
|
|
6
|
+
*/
|
|
7
|
+
import { isCancel, password } from '@clack/prompts'
|
|
8
|
+
import type { Hex } from 'viem'
|
|
9
|
+
import { decryptSecret } from './crypto'
|
|
10
|
+
import { readProfile, readSession, writeSession } from './store'
|
|
11
|
+
|
|
12
|
+
export async function tryProfileUnlock(agentAddress: string): Promise<Hex | null> {
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
const session = await readSession(now, agentAddress)
|
|
15
|
+
if (session) return session.privkey as Hex
|
|
16
|
+
|
|
17
|
+
const profile = await readProfile()
|
|
18
|
+
if (!profile || profile.address.toLowerCase() !== agentAddress.toLowerCase()) return null
|
|
19
|
+
|
|
20
|
+
const pw = await password({ message: 'Profile password' })
|
|
21
|
+
if (isCancel(pw)) return null
|
|
22
|
+
try {
|
|
23
|
+
const pk = decryptSecret(profile.cipher, String(pw))
|
|
24
|
+
await writeSession(profile.address, pk, now)
|
|
25
|
+
return pk as Hex
|
|
26
|
+
} catch {
|
|
27
|
+
return null // wrong password → caller falls back to operator unlock
|
|
28
|
+
}
|
|
29
|
+
}
|