@astrale-os/cli 0.4.0-alpha.13 → 0.6.0-alpha.0

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 (121) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +2 -2
  3. package/THIRD-PARTY-NOTICES.md +27 -0
  4. package/dist/astrale.js +24396 -9632
  5. package/package.json +24 -22
  6. package/src/command.ts +2 -0
  7. package/src/commands/__tests__/admin-instance.test.ts +3 -2
  8. package/src/commands/__tests__/domain-list.test.ts +6 -2
  9. package/src/commands/__tests__/help-contract.test.ts +27 -14
  10. package/src/commands/__tests__/install-identity-override.test.ts +2 -2
  11. package/src/commands/__tests__/ls.test.ts +1 -1
  12. package/src/commands/__tests__/read-commands.test.ts +201 -0
  13. package/src/commands/__tests__/view.test.ts +100 -0
  14. package/src/commands/call.ts +27 -44
  15. package/src/commands/describe.ts +57 -58
  16. package/src/commands/domain/install.ts +9 -9
  17. package/src/commands/domain/list.ts +2 -2
  18. package/src/commands/domain/publish.ts +3 -3
  19. package/src/commands/get.ts +48 -23
  20. package/src/commands/identity/register.ts +27 -33
  21. package/src/commands/instance/active.ts +2 -2
  22. package/src/commands/instance/create.ts +1 -1
  23. package/src/commands/instance/delete.ts +3 -3
  24. package/src/commands/instance/list.ts +8 -3
  25. package/src/commands/instance/status.ts +4 -3
  26. package/src/commands/instance/use.ts +2 -2
  27. package/src/commands/logs.ts +8 -8
  28. package/src/commands/ls.ts +77 -55
  29. package/src/commands/mutate.ts +191 -0
  30. package/src/commands/query.ts +307 -20
  31. package/src/commands/session/analyze.ts +50 -0
  32. package/src/commands/session/list.ts +36 -0
  33. package/src/commands/token.ts +4 -7
  34. package/src/commands/view-serve.ts +26 -0
  35. package/src/commands/view.ts +614 -0
  36. package/src/connect-core.test.ts +42 -0
  37. package/src/connect-core.ts +53 -0
  38. package/src/kernel/__tests__/auth.test.ts +1 -0
  39. package/src/kernel/__tests__/expand.test.ts +123 -0
  40. package/src/kernel/client.ts +33 -37
  41. package/src/kernel/expand.ts +55 -61
  42. package/src/kernel/graph.ts +96 -0
  43. package/src/kernel/index.ts +18 -3
  44. package/src/kernel/options.ts +1 -1
  45. package/src/kernel/run.ts +0 -13
  46. package/src/lib/__tests__/instance-target.test.ts +34 -0
  47. package/src/lib/__tests__/table.test.ts +1 -1
  48. package/src/lib/__tests__/view-open-intent.test.ts +308 -0
  49. package/src/lib/__tests__/view-snapshot.test.ts +77 -0
  50. package/src/lib/admin-domain.ts +8 -4
  51. package/src/lib/admin-instance.ts +5 -1
  52. package/src/lib/config.ts +1 -0
  53. package/src/lib/domain-identity.ts +1 -1
  54. package/src/lib/instance-target.ts +5 -0
  55. package/src/lib/instance.ts +11 -0
  56. package/src/lib/log.ts +12 -2
  57. package/src/lib/login-flow.ts +41 -4
  58. package/src/lib/provision-instance.ts +2 -2
  59. package/src/lib/self.ts +1 -3
  60. package/src/lib/view/open-intent.ts +97 -0
  61. package/src/lib/view/resolve.ts +104 -0
  62. package/src/lib/view/server.ts +294 -0
  63. package/src/lib/view/session.ts +123 -0
  64. package/src/lib/view/snapshot.ts +101 -0
  65. package/src/program.ts +16 -3
  66. package/src/registry.ts +1 -1
  67. package/src/setup/render.ts +1 -3
  68. package/src/setup/steps/instance.ts +2 -2
  69. package/src/telemetry/__tests__/gate.test.ts +63 -0
  70. package/src/telemetry/__tests__/recorder.test.ts +110 -0
  71. package/src/telemetry/__tests__/redact.test.ts +79 -0
  72. package/src/telemetry/__tests__/session.test.ts +166 -0
  73. package/src/telemetry/__tests__/trigger.test.ts +49 -0
  74. package/src/telemetry/adapters/__tests__/claude-code.test.ts +89 -0
  75. package/src/telemetry/adapters/__tests__/codex.test.ts +138 -0
  76. package/src/telemetry/adapters/__tests__/index.test.ts +88 -0
  77. package/src/telemetry/adapters/claude-code.ts +90 -0
  78. package/src/telemetry/adapters/codex.ts +160 -0
  79. package/src/telemetry/adapters/index.ts +45 -0
  80. package/src/telemetry/adapters/types.ts +21 -0
  81. package/src/telemetry/analyze.ts +227 -0
  82. package/src/telemetry/gate.ts +79 -0
  83. package/src/telemetry/recorder.ts +57 -0
  84. package/src/telemetry/redact.ts +53 -0
  85. package/src/telemetry/session.ts +88 -0
  86. package/src/telemetry/settings.ts +26 -0
  87. package/src/telemetry/store.ts +91 -0
  88. package/src/telemetry/trigger.ts +119 -0
  89. package/src/telemetry/types.ts +64 -0
  90. package/studio/client/dist/assets/index-CyN5G8IA.js +109 -0
  91. package/studio/client/dist/assets/index-DKKMHBBC.css +1 -0
  92. package/studio/client/dist/index.html +2 -2
  93. package/studio/server/agent/ask.ts +7 -2
  94. package/studio/server/agent/claude.ts +15 -3
  95. package/studio/server/agent/runner.ts +4 -1
  96. package/studio/server/agent/session-id.ts +13 -0
  97. package/studio/server/api.ts +50 -32
  98. package/studio/server/cache.ts +17 -6
  99. package/studio/server/client-package.test.ts +147 -0
  100. package/studio/server/client-package.ts +242 -0
  101. package/studio/server/index.ts +13 -0
  102. package/studio/server/introspect/anatomy-extras.test.ts +91 -0
  103. package/studio/server/introspect/anatomy-extras.ts +48 -49
  104. package/studio/server/introspect/anatomy.ts +7 -7
  105. package/studio/server/introspect/overlay-tsmorph.test.ts +104 -0
  106. package/studio/server/introspect/overlay-tsmorph.ts +230 -72
  107. package/studio/server/state/harness-gateway.ts +12 -3
  108. package/studio/server/state/harness-token.ts +0 -0
  109. package/studio/server/state/views.test.ts +90 -0
  110. package/studio/server/state/views.ts +396 -99
  111. package/studio/server/state/visibility.ts +10 -6
  112. package/studio/server/view-dev-server.test.ts +111 -0
  113. package/studio/server/view-dev-server.ts +372 -0
  114. package/studio/shared/types.ts +57 -10
  115. package/viewer/dist/index.html +93 -0
  116. package/viewer/dist/main.js +71 -0
  117. package/src/kernel/__tests__/remote-routing.test.ts +0 -70
  118. package/src/kernel/remote-routing.ts +0 -88
  119. package/studio/client/dist/assets/index-DOwzZAEK.css +0 -1
  120. package/studio/client/dist/assets/index-wtU0Zxhy.js +0 -183
  121. package/studio/tsconfig.json +0 -23
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Opportunistic analysis trigger — the `git gc --auto` model. Each CLI start
3
+ * cheaply scans for a closed, unanalyzed session and spawns ONE detached
4
+ * analyzer for it. No daemon, no cron; a lockfile keeps it single-flight.
5
+ */
6
+ import { spawn } from 'node:child_process'
7
+ import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'node:fs'
8
+ import { join } from 'node:path'
9
+
10
+ import { telemetryEnabled } from './settings'
11
+ import { listSessions, sessionDir, sessionsRoot, type SessionInfo } from './store'
12
+
13
+ const LOCK_STALE_MS = 30 * 60 * 1000
14
+ const GC_AGE_MS = 30 * 24 * 60 * 60 * 1000
15
+ const GC_MAX_PER_RUN = 20
16
+
17
+ /** Opportunistic GC: analyzed sessions idle past the retention age are removed
18
+ * (capped per invocation) so the store — and the per-invocation scan — stays
19
+ * bounded. Evidence worth keeping has left via reports or filed issues. */
20
+ function gcOldSessions(sessions: SessionInfo[]): void {
21
+ try {
22
+ const cutoff = Date.now() - GC_AGE_MS
23
+ let removed = 0
24
+ for (const s of sessions) {
25
+ if (removed >= GC_MAX_PER_RUN) break
26
+ if (s.analyzed !== null && s.lastEventAt !== null && s.lastEventAt.getTime() < cutoff) {
27
+ rmSync(sessionDir(s.id), { recursive: true, force: true })
28
+ removed++
29
+ }
30
+ }
31
+ } catch {
32
+ /* best effort */
33
+ }
34
+ }
35
+
36
+ function lockPath(): string {
37
+ return join(sessionsRoot(), '.analyzer.lock')
38
+ }
39
+
40
+ /** True when the lock is free (or stale/dead) and was claimed. */
41
+ function claimLock(): boolean {
42
+ const path = lockPath()
43
+ try {
44
+ if (existsSync(path)) {
45
+ const lock = JSON.parse(readFileSync(path, 'utf-8')) as { pid: number; at: number }
46
+ const stale = Date.now() - lock.at > LOCK_STALE_MS
47
+ let alive = false
48
+ try {
49
+ process.kill(lock.pid, 0)
50
+ alive = true
51
+ } catch {
52
+ /* dead */
53
+ }
54
+ if (alive && !stale) return false
55
+ unlinkSync(path)
56
+ }
57
+ mkdirSync(sessionsRoot(), { recursive: true })
58
+ writeFileSync(path, JSON.stringify({ pid: process.pid, at: Date.now() }))
59
+ return true
60
+ } catch {
61
+ return false
62
+ }
63
+ }
64
+
65
+ /** Release only in the parent that claimed; the child rewrites its own. */
66
+ export function releaseLock(): void {
67
+ try {
68
+ unlinkSync(lockPath())
69
+ } catch {
70
+ /* already gone */
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Fire-and-forget: spawn a detached `astrale session analyze <id> --auto` for
76
+ * the most recently closed unanalyzed session, if any. Never throws, never
77
+ * blocks — worst case a few ms of directory stats.
78
+ */
79
+ export function maybeTriggerAnalysis(argv: string[]): void {
80
+ try {
81
+ if (!telemetryEnabled()) return
82
+ if (process.env.ASTRALE_TELEMETRY_NO_TRIGGER === '1') return
83
+ // Never cascade off the session commands themselves.
84
+ if (argv[2] === 'session') return
85
+
86
+ const sessions = listSessions()
87
+ gcOldSessions(sessions)
88
+ const target = sessions.find((s) => s.closed && s.analyzed === null && s.lastEventAt !== null)
89
+ if (!target) return
90
+ if (!claimLock()) return
91
+
92
+ // Re-invoke self: execPath is bun/node (dev) or the compiled binary; when
93
+ // argv[1] is a real script path, keep it as the first argument. In a
94
+ // bun-compiled binary argv[1] is a virtual /$bunfs/ path that must NOT be
95
+ // passed — the binary re-runs itself from execPath alone.
96
+ const script = process.argv[1]
97
+ const args =
98
+ script && script !== process.execPath && !script.startsWith('/$bunfs') ? [script] : []
99
+ args.push('session', 'analyze', target.id, '--auto')
100
+ const child = spawn(process.execPath, args, {
101
+ detached: true,
102
+ stdio: 'ignore',
103
+ env: { ...process.env, ASTRALE_TELEMETRY_NO_TRIGGER: '1' },
104
+ })
105
+ child.unref()
106
+ // The lock hands over to the child: it re-stamps with its own pid on start.
107
+ } catch {
108
+ /* telemetry must never affect the CLI */
109
+ }
110
+ }
111
+
112
+ /** Called by `session analyze --auto` to take over the parent's claim. */
113
+ export function restampLock(): void {
114
+ try {
115
+ writeFileSync(lockPath(), JSON.stringify({ pid: process.pid, at: Date.now() }))
116
+ } catch {
117
+ /* best effort */
118
+ }
119
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Telemetry contracts. One CLI invocation = one appended event; one session =
3
+ * one directory of evidence under `~/.astrale/sessions/`; harness adapters
4
+ * contribute transcript discovery + a reading guide, never normalization.
5
+ */
6
+
7
+ /** One CLI invocation, one JSON line in events.jsonl. */
8
+ export type TelemetryEvent = {
9
+ /** Envelope version. */
10
+ v: 1
11
+ /** ISO timestamp at process start. */
12
+ ts: string
13
+ /** argv after the binary name, secrets redacted (see redact.ts). */
14
+ argv: string[]
15
+ exitCode: number
16
+ durationMs: number
17
+ /** Error class name when the command threw (AstraleError code preferred). */
18
+ errorName?: string
19
+ cwd: string
20
+ /** Workspace root the session is bucketed by (git root, else cwd). */
21
+ root: string
22
+ /** Which surface produced the event. */
23
+ surface: 'cli'
24
+ }
25
+
26
+ /** Written once when a session directory is created. */
27
+ export type SessionMeta = {
28
+ id: string
29
+ root: string
30
+ /** true when the id came from ASTRALE_SESSION (a surface owns the lifecycle). */
31
+ explicit: boolean
32
+ createdAt: string
33
+ }
34
+
35
+ /** Written by the analyzer when it finishes with a session (any outcome). */
36
+ export type AnalyzedMarker = {
37
+ analyzedAt: string
38
+ outcome: 'skipped-quiet' | 'reported' | 'filed' | 'error'
39
+ note?: string
40
+ }
41
+
42
+ /** An agent-harness session discovered on this machine (transcript = evidence). */
43
+ export type HarnessSession = {
44
+ harness: string
45
+ sessionId?: string
46
+ transcriptPath: string
47
+ cwd?: string
48
+ startedAt?: string
49
+ endedAt?: string
50
+ sizeBytes: number
51
+ }
52
+
53
+ /** Deterministic signals extracted from events.jsonl — the free gate's input. */
54
+ export type SessionSignals = {
55
+ eventCount: number
56
+ /** Events with a non-zero exit, grouped by leading command tokens. */
57
+ failures: { command: string; count: number; errorNames: string[] }[]
58
+ /** Same leading command tokens re-run 3+ times (retry smell). */
59
+ retries: { command: string; count: number }[]
60
+ firstEventAt?: string
61
+ lastEventAt?: string
62
+ /** Harness transcripts overlapping this session's root + time window. */
63
+ harnessSessions: HarnessSession[]
64
+ }