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.
Files changed (60) hide show
  1. package/README.md +39 -0
  2. package/bin/nebula +11 -0
  3. package/package.json +50 -0
  4. package/src/commands/_agents.ts +14 -0
  5. package/src/commands/_unlock.ts +66 -0
  6. package/src/commands/agent-wallet.ts +90 -0
  7. package/src/commands/chat-telegram.ts +398 -0
  8. package/src/commands/chat.tsx +1308 -0
  9. package/src/commands/drain.ts +90 -0
  10. package/src/commands/gateway-logs.ts +49 -0
  11. package/src/commands/gateway-run.ts +42 -0
  12. package/src/commands/gateway-start.ts +216 -0
  13. package/src/commands/gateway-status.ts +90 -0
  14. package/src/commands/gateway-stop.ts +133 -0
  15. package/src/commands/gateway.ts +101 -0
  16. package/src/commands/identity.ts +178 -0
  17. package/src/commands/init/cost.ts +40 -0
  18. package/src/commands/init/funding-gate.ts +64 -0
  19. package/src/commands/init/model-picker.ts +25 -0
  20. package/src/commands/init/operator-picker.ts +233 -0
  21. package/src/commands/init/telegram-step.ts +245 -0
  22. package/src/commands/init/wizard-state.ts +94 -0
  23. package/src/commands/init.ts +439 -0
  24. package/src/commands/login.ts +86 -0
  25. package/src/commands/logs.ts +37 -0
  26. package/src/commands/model.ts +48 -0
  27. package/src/commands/pairing-approve.ts +65 -0
  28. package/src/commands/pairing-clear.ts +39 -0
  29. package/src/commands/pairing-list.ts +55 -0
  30. package/src/commands/pairing-revoke.ts +49 -0
  31. package/src/commands/pairing.ts +81 -0
  32. package/src/commands/status.ts +44 -0
  33. package/src/commands/telegram-remove.ts +62 -0
  34. package/src/commands/telegram-setup.ts +64 -0
  35. package/src/commands/telegram-status.ts +87 -0
  36. package/src/commands/telegram.ts +44 -0
  37. package/src/commands/trust.ts +196 -0
  38. package/src/config/load.ts +35 -0
  39. package/src/config/render.ts +99 -0
  40. package/src/index.ts +184 -0
  41. package/src/profile/crypto.ts +68 -0
  42. package/src/profile/derive.ts +25 -0
  43. package/src/profile/store.ts +86 -0
  44. package/src/profile/unlock.ts +29 -0
  45. package/src/ui/app.tsx +719 -0
  46. package/src/ui/approval-summary.ts +32 -0
  47. package/src/ui/markdown-parse.ts +219 -0
  48. package/src/ui/markdown.tsx +37 -0
  49. package/src/ui/state.ts +181 -0
  50. package/src/util/bootstrap-mode.ts +25 -0
  51. package/src/util/bootstrap-progress-box.ts +378 -0
  52. package/src/util/cli-version.ts +28 -0
  53. package/src/util/format.ts +11 -0
  54. package/src/util/gateway-spawn.ts +125 -0
  55. package/src/util/gateway-version.ts +154 -0
  56. package/src/util/github-releases.ts +79 -0
  57. package/src/util/profile-key.ts +25 -0
  58. package/src/util/ref-resolver.ts +55 -0
  59. package/src/util/silence-console.ts +40 -0
  60. 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
+ }