@astrale-os/cli 0.8.0-alpha.0 → 0.8.1-alpha.2

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 (317) hide show
  1. package/README.md +4 -1
  2. package/dist/astrale.js +62968 -41240
  3. package/dist/public/connect-core.js +55030 -0
  4. package/dist/public/keys/index.js +45631 -0
  5. package/dist/public/paths/index.js +43990 -0
  6. package/dist/types/admin/instance/model.d.ts +40 -0
  7. package/dist/types/connect-core.d.ts +22 -0
  8. package/dist/types/connection/auth.d.ts +33 -0
  9. package/dist/types/errors.d.ts +23 -0
  10. package/dist/types/identity/registry.d.ts +25 -0
  11. package/dist/types/keys/algorithm.d.ts +6 -0
  12. package/dist/types/keys/credential.d.ts +21 -0
  13. package/dist/types/keys/index.d.ts +4 -0
  14. package/dist/types/keys/pair.d.ts +34 -0
  15. package/dist/types/lib/admin-target.d.ts +74 -0
  16. package/dist/types/lib/ca-fetch.d.ts +2 -0
  17. package/dist/types/lib/config.d.ts +71 -0
  18. package/dist/types/lib/format.d.ts +1 -0
  19. package/dist/types/lib/idp-session.d.ts +33 -0
  20. package/dist/types/lib/idp.d.ts +397 -0
  21. package/dist/types/lib/instance-target.d.ts +36 -0
  22. package/dist/types/lib/instance.d.ts +186 -0
  23. package/dist/types/lib/log.d.ts +30 -0
  24. package/dist/types/lib/login-flow.d.ts +49 -0
  25. package/dist/types/lib/meta.d.ts +36 -0
  26. package/dist/types/lib/output.d.ts +83 -0
  27. package/dist/types/lib/table.d.ts +18 -0
  28. package/dist/types/lib/validation.d.ts +11 -0
  29. package/dist/types/paths/index.d.ts +2 -0
  30. package/dist/types/state/exchange-credentials.d.ts +25 -0
  31. package/dist/types/state/files.d.ts +9 -0
  32. package/dist/types/state/identities.d.ts +42 -0
  33. package/dist/types/state/index.d.ts +8 -0
  34. package/dist/types/state/paths.d.ts +32 -0
  35. package/package.json +30 -8
  36. package/src/admin/.spec/api.d.ts +3 -0
  37. package/src/admin/.spec/architecture.md +15 -0
  38. package/src/admin/.spec/icon.svg +3 -0
  39. package/src/admin/.spec/layout.ts +6 -0
  40. package/src/admin/catalog/.spec/api.d.ts +65 -0
  41. package/src/admin/catalog/.spec/architecture.md +6 -0
  42. package/src/admin/catalog/.spec/icon.svg +3 -0
  43. package/src/admin/catalog/.spec/layout.ts +6 -0
  44. package/src/admin/catalog/__tests__/client.test.ts +171 -0
  45. package/src/admin/catalog/client.ts +214 -0
  46. package/src/admin/catalog/index.ts +13 -0
  47. package/src/admin/catalog/model.ts +42 -0
  48. package/src/admin/catalog/tsconfig.json +8 -0
  49. package/src/admin/graph/.spec/api.d.ts +28 -0
  50. package/src/admin/graph/.spec/architecture.md +5 -0
  51. package/src/admin/graph/.spec/icon.svg +3 -0
  52. package/src/admin/graph/.spec/layout.ts +3 -0
  53. package/src/admin/graph/index.ts +6 -0
  54. package/src/admin/graph/nodes.ts +56 -0
  55. package/src/admin/index.ts +2 -0
  56. package/src/admin/instance/.spec/api.d.ts +66 -0
  57. package/src/admin/instance/.spec/architecture.md +10 -0
  58. package/src/admin/instance/.spec/icon.svg +3 -0
  59. package/src/admin/instance/.spec/layout.ts +6 -0
  60. package/src/admin/instance/__tests__/client.test.ts +289 -0
  61. package/src/admin/instance/client.ts +298 -0
  62. package/src/admin/instance/index.ts +16 -0
  63. package/src/admin/instance/model.ts +65 -0
  64. package/src/admin/instance/tsconfig.json +8 -0
  65. package/src/commands/__tests__/admin-instance.test.ts +3 -36
  66. package/src/commands/__tests__/call.test.ts +21 -68
  67. package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
  68. package/src/commands/__tests__/domain-list.test.ts +2 -6
  69. package/src/commands/__tests__/logs.test.ts +50 -98
  70. package/src/commands/__tests__/ls.test.ts +21 -17
  71. package/src/commands/__tests__/read-commands.test.ts +171 -101
  72. package/src/commands/__tests__/view.test.ts +100 -51
  73. package/src/commands/admin/status.ts +3 -3
  74. package/src/commands/admin/use.ts +29 -6
  75. package/src/commands/auth/login.ts +1 -1
  76. package/src/commands/auth/logout.ts +4 -2
  77. package/src/commands/auth/status.ts +2 -2
  78. package/src/commands/auth/token.ts +2 -2
  79. package/src/commands/browser.ts +1 -1
  80. package/src/commands/call.ts +55 -91
  81. package/src/commands/describe.ts +52 -147
  82. package/src/commands/domain/install.ts +81 -50
  83. package/src/commands/domain/list.ts +4 -8
  84. package/src/commands/domain/publish.ts +9 -31
  85. package/src/commands/get.ts +32 -65
  86. package/src/commands/identity/__tests__/register.test.ts +93 -0
  87. package/src/commands/identity/create.ts +2 -2
  88. package/src/commands/identity/delete.ts +2 -2
  89. package/src/commands/identity/export.ts +7 -33
  90. package/src/commands/identity/import.ts +21 -70
  91. package/src/commands/identity/list.ts +2 -2
  92. package/src/commands/identity/register.ts +148 -117
  93. package/src/commands/identity/sync.ts +2 -2
  94. package/src/commands/identity/unsync.ts +2 -2
  95. package/src/commands/identity/use.ts +2 -2
  96. package/src/commands/identity/whoami.ts +2 -2
  97. package/src/commands/idp/add.ts +1 -1
  98. package/src/commands/idp/list.ts +1 -1
  99. package/src/commands/idp/refresh.ts +1 -1
  100. package/src/commands/idp/remove.ts +2 -2
  101. package/src/commands/idp/show.ts +1 -1
  102. package/src/commands/instance/active.ts +2 -2
  103. package/src/commands/instance/bookmark.ts +8 -2
  104. package/src/commands/instance/create.ts +1 -1
  105. package/src/commands/instance/delete.ts +4 -10
  106. package/src/commands/instance/forget.ts +1 -1
  107. package/src/commands/instance/list.ts +3 -3
  108. package/src/commands/instance/status.ts +5 -7
  109. package/src/commands/instance/use.ts +3 -3
  110. package/src/commands/logs.ts +152 -279
  111. package/src/commands/ls.ts +78 -204
  112. package/src/commands/mutate.ts +70 -139
  113. package/src/commands/query.ts +100 -278
  114. package/src/commands/session/analyze.ts +1 -1
  115. package/src/commands/session/list.ts +1 -1
  116. package/src/commands/setup.ts +1 -1
  117. package/src/commands/status.ts +1 -1
  118. package/src/commands/studio.ts +1 -1
  119. package/src/commands/token.ts +26 -21
  120. package/src/commands/update.ts +1 -1
  121. package/src/commands/use.ts +2 -3
  122. package/src/commands/view-serve.ts +1 -1
  123. package/src/commands/view.ts +67 -109
  124. package/src/connect-core.test.ts +38 -0
  125. package/src/connect-core.ts +42 -0
  126. package/src/connection/.spec/api.d.ts +111 -0
  127. package/src/connection/.spec/architecture.md +41 -0
  128. package/src/connection/.spec/capabilities/connection.ts +13 -0
  129. package/src/connection/.spec/examples/call.ts +7 -0
  130. package/src/connection/.spec/flows/command.ts +20 -0
  131. package/src/connection/.spec/flows/session.ts +61 -0
  132. package/src/connection/.spec/laws/connection.ts +181 -0
  133. package/src/connection/.spec/layout.ts +19 -0
  134. package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
  135. package/src/connection/__tests__/ca-fetch.test.ts +53 -0
  136. package/src/connection/__tests__/call.test.ts +17 -0
  137. package/src/connection/__tests__/credential.test.ts +109 -0
  138. package/src/connection/__tests__/errors.test.ts +96 -0
  139. package/src/connection/__tests__/exchange.test.ts +224 -0
  140. package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
  141. package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
  142. package/src/connection/__tests__/managed.test.ts +39 -0
  143. package/src/connection/__tests__/self.test.ts +51 -0
  144. package/src/connection/__tests__/session.test.ts +140 -0
  145. package/src/connection/__tests__/target.test.ts +93 -0
  146. package/src/{kernel → connection}/auth.ts +8 -8
  147. package/src/connection/call.ts +7 -0
  148. package/src/{kernel/run.ts → connection/command.ts} +22 -13
  149. package/src/connection/credential.ts +114 -0
  150. package/src/{kernel → connection}/errors.ts +32 -9
  151. package/src/connection/exchange.ts +274 -0
  152. package/src/connection/index.ts +8 -0
  153. package/src/connection/self.ts +120 -0
  154. package/src/connection/session.ts +176 -0
  155. package/src/connection/target.ts +111 -0
  156. package/src/connection/tsconfig.json +6 -0
  157. package/src/graph/.spec/api.d.ts +34 -0
  158. package/src/graph/.spec/laws/documents.ts +24 -0
  159. package/src/graph/.spec/laws/projection.ts +13 -0
  160. package/src/graph/.spec/layout.ts +6 -0
  161. package/src/graph/__tests__/mutation.test.ts +43 -0
  162. package/src/graph/__tests__/projection.test.ts +22 -0
  163. package/src/graph/__tests__/query.test.ts +154 -0
  164. package/src/graph/index.ts +3 -0
  165. package/src/graph/mutation.ts +18 -0
  166. package/src/graph/projection.ts +21 -0
  167. package/src/graph/query.ts +108 -0
  168. package/src/graph/tsconfig.json +6 -0
  169. package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
  170. package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
  171. package/src/identity/.spec/api.d.ts +124 -0
  172. package/src/identity/.spec/architecture.md +33 -0
  173. package/src/identity/.spec/capabilities/identity.ts +7 -0
  174. package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
  175. package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
  176. package/src/identity/.spec/flows/import.ts +29 -0
  177. package/src/identity/.spec/laws/identity.ts +56 -0
  178. package/src/identity/.spec/layout.ts +13 -0
  179. package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
  180. package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
  181. package/src/identity/__tests__/registration.test.ts +64 -0
  182. package/src/identity/__tests__/registry.test.ts +70 -0
  183. package/src/identity/__tests__/transfer.test.ts +198 -0
  184. package/src/identity/index.ts +23 -0
  185. package/src/identity/registration.ts +72 -0
  186. package/src/identity/registry.ts +172 -0
  187. package/src/identity/transfer.ts +187 -0
  188. package/src/identity/tsconfig.json +6 -0
  189. package/src/index.ts +1 -0
  190. package/src/keys/.spec/api.d.ts +74 -0
  191. package/src/keys/.spec/architecture.md +13 -0
  192. package/src/keys/.spec/laws/keys.ts +43 -0
  193. package/src/keys/.spec/layout.ts +6 -0
  194. package/src/keys/__tests__/algorithm.test.ts +39 -0
  195. package/src/keys/__tests__/keys.test.ts +168 -0
  196. package/src/keys/algorithm.ts +33 -0
  197. package/src/keys/credential.ts +112 -0
  198. package/src/keys/index.ts +14 -0
  199. package/src/keys/pair.ts +219 -0
  200. package/src/keys/tsconfig.json +6 -0
  201. package/src/lib/__tests__/admin-target.test.ts +67 -7
  202. package/src/lib/__tests__/binary.test.ts +2 -3
  203. package/src/lib/__tests__/command-dx.test.ts +2 -2
  204. package/src/lib/__tests__/config.test.ts +3 -2
  205. package/src/lib/__tests__/idp.test.ts +4 -6
  206. package/src/lib/__tests__/instance-target.test.ts +7 -4
  207. package/src/lib/__tests__/local-status.test.ts +1 -1
  208. package/src/lib/__tests__/log.test.ts +31 -0
  209. package/src/lib/__tests__/meta.test.ts +55 -0
  210. package/src/lib/__tests__/sdk-deps.test.ts +1 -1
  211. package/src/lib/__tests__/self.test.ts +8 -230
  212. package/src/lib/__tests__/use-target.test.ts +1 -1
  213. package/src/lib/__tests__/view-open-intent.test.ts +78 -104
  214. package/src/lib/__tests__/view-session.test.ts +99 -0
  215. package/src/lib/admin-domain.ts +39 -25
  216. package/src/lib/admin-instance.ts +38 -43
  217. package/src/lib/admin-target.ts +67 -13
  218. package/src/lib/binary.ts +13 -34
  219. package/src/lib/browser.ts +1 -1
  220. package/src/{kernel → lib}/ca-fetch.ts +35 -27
  221. package/src/lib/config.ts +1 -1
  222. package/src/lib/idp-session.ts +1 -1
  223. package/src/lib/idp.ts +3 -8
  224. package/src/lib/instance-target.ts +12 -8
  225. package/src/lib/instance.ts +34 -2
  226. package/src/lib/local-status.ts +2 -2
  227. package/src/lib/log.ts +3 -1
  228. package/src/lib/login-flow.ts +32 -5
  229. package/src/lib/meta.ts +15 -11
  230. package/src/lib/provision-instance.ts +4 -12
  231. package/src/lib/self.ts +5 -147
  232. package/src/lib/update.ts +1 -1
  233. package/src/lib/use-target.ts +1 -1
  234. package/src/lib/validation.ts +2 -2
  235. package/src/lib/view/open-intent.ts +7 -37
  236. package/src/lib/view/port-allocation.ts +1 -1
  237. package/src/lib/view/resolve.ts +52 -45
  238. package/src/lib/view/server.ts +16 -33
  239. package/src/lib/view/session.ts +51 -22
  240. package/src/paths/.spec/api.d.ts +13 -0
  241. package/src/paths/.spec/architecture.md +5 -0
  242. package/src/paths/.spec/layout.ts +3 -0
  243. package/src/paths/index.ts +13 -0
  244. package/src/program/.spec/api.d.ts +42 -0
  245. package/src/program/.spec/architecture.md +18 -0
  246. package/src/program/.spec/laws/program.ts +29 -0
  247. package/src/program/.spec/layout.ts +14 -0
  248. package/src/program/__tests__/program.test.ts +359 -0
  249. package/src/program/build.ts +190 -0
  250. package/src/program/command.ts +42 -0
  251. package/src/program/index.ts +2 -0
  252. package/src/program/options.ts +40 -0
  253. package/src/{registry.ts → program/registry.ts} +5 -1
  254. package/src/program/tsconfig.json +6 -0
  255. package/src/setup/steps/admin.ts +18 -3
  256. package/src/setup/steps/instance.ts +1 -1
  257. package/src/state/.spec/api.d.ts +133 -0
  258. package/src/state/.spec/architecture.md +31 -0
  259. package/src/state/.spec/capabilities/state.ts +7 -0
  260. package/src/state/.spec/flows/identity-update.ts +22 -0
  261. package/src/state/.spec/laws/state.ts +73 -0
  262. package/src/state/.spec/layout.ts +14 -0
  263. package/src/state/__tests__/exchange-credentials.test.ts +188 -0
  264. package/src/state/__tests__/files.test.ts +113 -0
  265. package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
  266. package/src/state/__tests__/identities.test.ts +142 -0
  267. package/src/state/__tests__/paths.test.ts +27 -0
  268. package/src/state/exchange-credentials.ts +219 -0
  269. package/src/state/files.ts +129 -0
  270. package/src/state/identities.ts +236 -0
  271. package/src/state/index.ts +29 -0
  272. package/src/state/paths.ts +61 -0
  273. package/src/state/tsconfig.json +6 -0
  274. package/src/telemetry/__tests__/redact.test.ts +3 -3
  275. package/src/telemetry/__tests__/trigger.test.ts +1 -1
  276. package/src/telemetry/settings.ts +1 -1
  277. package/src/telemetry/store.ts +1 -1
  278. package/src/test-utils.ts +1 -1
  279. package/studio/client/dist/assets/elk-api-D0cBetPW.js +1 -0
  280. package/studio/client/dist/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  281. package/studio/client/dist/assets/index-BQnJ5sgd.css +1 -0
  282. package/studio/client/dist/assets/index-ChOr3yP0.css +1 -0
  283. package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
  284. package/studio/client/dist/assets/index-bVD2KJgz.js +8 -0
  285. package/studio/client/dist/index.html +2 -2
  286. package/studio/package.json +10 -1
  287. package/studio/server/introspect/extractor.ts +1 -1
  288. package/studio/server/state/views.test.ts +3 -7
  289. package/studio/server/state/views.ts +23 -30
  290. package/studio/tsconfig.json +23 -0
  291. package/tsconfig.json +1 -1
  292. package/viewer/dist/main.js +65 -39
  293. package/viewer/tsconfig.json +13 -0
  294. package/src/command.ts +0 -42
  295. package/src/commands/__tests__/help-contract.test.ts +0 -164
  296. package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
  297. package/src/kernel/__tests__/errors.test.ts +0 -43
  298. package/src/kernel/__tests__/expand.test.ts +0 -123
  299. package/src/kernel/client.ts +0 -214
  300. package/src/kernel/expand.ts +0 -192
  301. package/src/kernel/graph.ts +0 -96
  302. package/src/kernel/index.ts +0 -29
  303. package/src/kernel/options.ts +0 -22
  304. package/src/kernel/types.ts +0 -14
  305. package/src/lib/__tests__/domain-identity.test.ts +0 -60
  306. package/src/lib/__tests__/fs-atomic.test.ts +0 -104
  307. package/src/lib/__tests__/identity.test.ts +0 -79
  308. package/src/lib/__tests__/keys.test.ts +0 -129
  309. package/src/lib/domain-identity.ts +0 -49
  310. package/src/lib/env.ts +0 -49
  311. package/src/lib/fs-atomic.ts +0 -126
  312. package/src/lib/identity.ts +0 -256
  313. package/src/lib/keys.ts +0 -294
  314. package/src/lib/paths.ts +0 -11
  315. package/src/program.ts +0 -213
  316. package/studio/client/dist/assets/index-BaqEuIQJ.js +0 -109
  317. package/studio/client/dist/assets/index-jRdExahh.css +0 -1
@@ -1,10 +1,10 @@
1
- import type { CommandDefinition } from '../../command'
2
1
  import type { OwnedInstanceInfo } from '../../lib/admin-instance'
2
+ import type { CommandDefinition } from '../../program/index'
3
3
 
4
4
  import { AstraleError } from '../../errors'
5
- import { listOwnedInstances } from '../../kernel/client'
5
+ import { getDefault, setDefault } from '../../identity/index'
6
+ import { listOwnedInstances } from '../../lib/admin-instance'
6
7
  import { ADMIN_TARGET_OPTIONS } from '../../lib/admin-target'
7
- import { getDefault, setDefault } from '../../lib/identity'
8
8
  import {
9
9
  getActive,
10
10
  readInstances,
@@ -1,347 +1,220 @@
1
- import type { JournalEntry, JournalFilter } from '@astrale-os/kernel-core'
2
-
3
- /**
4
- * `astrale logs` — tail the kernel event journal (function.journal) for the target
5
- * instance. Defaults to the kernel journal syscall
6
- * `/kernel.astrale.ai/functions/journal`; `--service <name>` switches to the
7
- * per-instance `services` domain log buffer (the historical behavior).
8
- *
9
- * Target the instance with `-i <instance>` (inherited from withKernelOptions).
10
- */
1
+ import { Path } from '@astrale-os/sdk/graph/path'
2
+ import { syscalls } from '@astrale-os/sdk/schema/kernel'
11
3
  import chalk from 'chalk'
12
4
 
13
- import type { CommandDefinition } from '../command'
14
- import type { ClientContext, KernelCommandOpts } from '../kernel'
5
+ import type { ConnectionContext, KernelCommandOpts } from '../connection'
15
6
  import type { Column, ListProjection } from '../lib/output'
7
+ import type { CommandDefinition } from '../program/index'
16
8
 
17
- import { runKernelCommand, withKernelClient } from '../kernel'
18
- import { fatal, withSpinner } from '../lib/log'
9
+ import { createPathCall, runKernelCommand, withClientSession } from '../connection'
19
10
  import { isMachine, output, presentList } from '../lib/output'
20
11
 
21
- const JOURNAL_FN_PATH = '/kernel.astrale.ai/functions/journal'
22
- const DEFAULT_SERVICES_ORIGIN = 'services.astrale.ai'
12
+ const JOURNAL_PATH = Path.project(syscalls.journal.ref).raw
23
13
  const DEFAULT_LIMIT = 200
24
- const FOLLOW_INTERVAL_MS = 2000
25
- // The journal-read syscall journals its own ops; hide them by default so polling
26
- // doesn't self-pollute the view. `--all` shows them.
27
- const SELF_READ_PREFIX = 'op:function.journal:'
28
-
29
- // The published @astrale-os/kernel-core@0.5.0 JournalFilter has no `cursor` yet
30
- // (added on the kernel branch). Type against the augmented shape so the
31
- // incremental cursor compiles against the consumer dependency.
32
- type EventsParams = JournalFilter & { cursor?: number }
14
+ const FOLLOW_INTERVAL_MS = 2_000
33
15
 
34
16
  type LogsOpts = KernelCommandOpts & {
35
17
  since?: string
36
18
  until?: string
37
19
  topic?: string
20
+ topicPrefix?: string
38
21
  principal?: string
39
22
  limit?: string
40
23
  cursor?: string
41
24
  follow?: boolean
42
- all?: boolean
43
- timing?: boolean
44
- // service mode
45
- service?: string
46
- tail?: string
47
- servicesOrigin?: string
48
- }
49
-
50
- /**
51
- * Kernel journal page. The function.journal syscall returns a BARE `JournalEntry[]`;
52
- * the client derives the next cursor from the max `seq`. We still model a
53
- * `nextCursor` field so the TTY footer / incremental tail share one shape, and
54
- * so a future paged kernel response degrades gracefully.
55
- */
56
- type EventsPage = {
57
- entries: JournalEntry[]
58
- nextCursor?: number | null
59
- }
60
-
61
- // ── function.journal (default) ───────────────────────────────────
62
-
63
- /** The highest `seq` across entries, or null when empty. */
64
- function maxSeq(entries: JournalEntry[]): number | null {
65
- let max: number | null = null
66
- for (const e of entries) {
67
- if (typeof e.seq === 'number' && (max === null || e.seq > max)) max = e.seq
68
- }
69
- return max
70
25
  }
71
26
 
72
- /**
73
- * Accept the bare `JournalEntry[]` the syscall returns (and tolerate a paged
74
- * `{ entries, nextCursor }` shape if the kernel ever switches). `nextCursor` is
75
- * the max `seq` — the client passes it back as `cursor` to tail incrementally.
76
- */
77
- export function normalizePage(raw: unknown): EventsPage {
78
- if (Array.isArray(raw)) {
79
- const entries = raw as JournalEntry[]
80
- return { entries, nextCursor: maxSeq(entries) }
81
- }
82
- const obj = (raw ?? {}) as { entries?: unknown; nextCursor?: unknown }
83
- const entries = Array.isArray(obj.entries) ? (obj.entries as JournalEntry[]) : []
84
- const nextCursor = typeof obj.nextCursor === 'number' ? obj.nextCursor : maxSeq(entries)
85
- return { entries, nextCursor }
27
+ export interface JournalRecord {
28
+ readonly sequence: number
29
+ readonly timestamp: string
30
+ readonly topic: string
31
+ readonly payload: unknown
32
+ readonly principal?: string
33
+ readonly correlationId?: string
34
+ readonly causationId?: string
86
35
  }
87
36
 
88
- /** Build the JournalFilter (+ optional seq cursor) from typed flags. */
89
- export function buildEventsParams(opts: LogsOpts): EventsParams {
90
- const params: EventsParams = {}
91
- if (opts.topic) params.topic = opts.topic
92
- if (opts.principal) params.principal = opts.principal as JournalFilter['principal']
93
- if (opts.since !== undefined) params.since = parseTimeFlag('--since', opts.since)
94
- if (opts.until !== undefined) params.until = parseTimeFlag('--until', opts.until)
95
- params.limit = opts.limit !== undefined ? parsePositiveInt('--limit', opts.limit) : DEFAULT_LIMIT
96
- if (opts.cursor !== undefined) params.cursor = parsePositiveInt('--cursor', opts.cursor)
97
- return params
37
+ export interface JournalPage {
38
+ readonly records: readonly JournalRecord[]
39
+ readonly cursor?: string
98
40
  }
99
41
 
100
- /** Accept epoch-ms or an ISO-8601 string → epoch ms. */
101
- export function parseTimeFlag(flag: string, raw: string): number {
102
- if (/^-?\d+$/.test(raw)) return Number(raw)
103
- const ms = Date.parse(raw)
104
- if (Number.isNaN(ms)) throw new Error(`${flag} needs epoch-ms or ISO-8601, got "${raw}"`)
105
- return ms
42
+ export interface JournalInput {
43
+ readonly topics?: { readonly exact?: readonly string[]; readonly prefixes?: readonly string[] }
44
+ readonly principal?: string
45
+ readonly since?: string
46
+ readonly until?: string
47
+ readonly cursor?: string
48
+ readonly limit: number
106
49
  }
107
50
 
108
- function parsePositiveInt(flag: string, raw: string): number {
109
- const n = Number(raw)
110
- if (!Number.isInteger(n) || n <= 0) {
111
- throw new Error(`${flag} needs a positive integer, got "${raw}"`)
51
+ /** Map flags to the exact public journal syscall input without legacy glob/sequence lowering. */
52
+ export function buildJournalInput(opts: LogsOpts): JournalInput {
53
+ const exact = nonEmpty(opts.topic)
54
+ const prefix = nonEmpty(opts.topicPrefix)
55
+ return {
56
+ ...(exact === undefined && prefix === undefined
57
+ ? {}
58
+ : {
59
+ topics: {
60
+ ...(exact === undefined ? {} : { exact: [exact] }),
61
+ ...(prefix === undefined ? {} : { prefixes: [prefix] }),
62
+ },
63
+ }),
64
+ ...(nonEmpty(opts.principal) === undefined ? {} : { principal: opts.principal }),
65
+ ...(nonEmpty(opts.since) === undefined ? {} : { since: opts.since }),
66
+ ...(nonEmpty(opts.until) === undefined ? {} : { until: opts.until }),
67
+ ...(nonEmpty(opts.cursor) === undefined ? {} : { cursor: opts.cursor }),
68
+ limit: opts.limit === undefined ? DEFAULT_LIMIT : positiveInteger('--limit', opts.limit),
112
69
  }
113
- return n
114
70
  }
115
71
 
116
- const TOPIC_COLOR = (s: string): string =>
117
- s.startsWith('op:') ? chalk.cyan(s) : s.startsWith('sys:') ? chalk.magenta(s) : chalk.dim(s)
118
-
119
- /** op:*:completed|failed carry `durationMs` in their payload; started does not. */
120
- const latencyOf = (e: JournalEntry): string => {
121
- const d = (e.event.payload as { durationMs?: number } | undefined)?.durationMs
122
- return typeof d === 'number' ? `${d}ms` : ''
72
+ /** Validate the record fields the CLI presentation consumes and retain the opaque cursor. */
73
+ export function acceptJournalPage(input: unknown): JournalPage {
74
+ if (!isRecord(input) || !Array.isArray(input.records)) {
75
+ throw new TypeError('Kernel journal response must contain a records array')
76
+ }
77
+ const records = input.records.map((record, index) => acceptRecord(record, index))
78
+ if (input.cursor !== undefined && typeof input.cursor !== 'string') {
79
+ throw new TypeError('Kernel journal cursor must be text')
80
+ }
81
+ return Object.freeze({
82
+ records: Object.freeze(records),
83
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
84
+ })
123
85
  }
124
86
 
125
- /** Highlight slow ops: green < 100ms, yellow < 500ms, red beyond. */
126
- const LATENCY_COLOR = (s: string): string => {
127
- if (!s) return s
128
- const ms = Number.parseInt(s, 10)
129
- if (ms >= 500) return chalk.red(s)
130
- if (ms >= 100) return chalk.yellow(s)
131
- return chalk.green(s)
87
+ async function fetchPage(context: ConnectionContext, opts: LogsOpts): Promise<JournalPage> {
88
+ return acceptJournalPage(
89
+ await context.session.call(createPathCall(JOURNAL_PATH, buildJournalInput(opts))),
90
+ )
132
91
  }
133
92
 
134
- /** Short labels for the per-step dispatch timing (payload.timing). */
135
- const STEP_LABEL: Record<string, string> = {
136
- authenticate: 'auth',
137
- validateInput: 'in',
138
- authorize: 'authz',
139
- resolve: 'resolve',
140
- invariants: 'inv',
141
- execute: 'exec',
142
- validateOutput: 'out',
143
- effects: 'fx',
93
+ async function runOnce(opts: LogsOpts): Promise<void> {
94
+ await runKernelCommand({
95
+ opts,
96
+ label: 'Kernel journal',
97
+ fn: (context) => fetchPage(context, opts),
98
+ format: (page, format) => {
99
+ if (isMachine(format) || format.format !== undefined) output(page, format)
100
+ else {
101
+ presentList([...page.records], format, journalProjection)
102
+ if (page.cursor) process.stderr.write(` cursor: ${page.cursor}\n`)
103
+ }
104
+ },
105
+ })
144
106
  }
145
107
 
146
- /** Compact per-step breakdown, non-zero steps only: e.g. "auth:7 authz:13 exec:12". */
147
- const timingOf = (e: JournalEntry): string => {
148
- const t = (e.event.payload as { timing?: Record<string, number> } | undefined)?.timing
149
- if (!t || typeof t !== 'object') return ''
150
- return Object.entries(t)
151
- .filter(([, ms]) => typeof ms === 'number' && ms > 0)
152
- .map(([k, ms]) => `${STEP_LABEL[k] ?? k}:${ms}`)
153
- .join(' ')
108
+ async function follow(opts: LogsOpts): Promise<void> {
109
+ await withClientSession(opts, async (context) => {
110
+ let cursor = opts.cursor
111
+ for (;;) {
112
+ const page = await fetchPage(context, { ...opts, cursor })
113
+ for (const record of page.records) printRecord(record)
114
+ cursor = page.cursor ?? cursor
115
+ await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS))
116
+ }
117
+ })
154
118
  }
155
119
 
156
- function eventsProjection(entries: JournalEntry[], showTiming = false): ListProjection {
120
+ function journalProjection(records: JournalRecord[]): ListProjection {
157
121
  const columns: Column[] = [
158
- { key: 'seq', header: 'SEQ', color: chalk.dim },
159
- { key: 'ts', header: 'TIME', color: chalk.dim },
160
- { key: 'topic', header: 'TOPIC', color: TOPIC_COLOR },
161
- { key: 'latency', header: 'LATENCY', color: LATENCY_COLOR },
162
- ...(showTiming ? [{ key: 'steps', header: 'STEPS', color: chalk.dim } as Column] : []),
122
+ { key: 'sequence', header: 'SEQ', color: chalk.dim },
123
+ { key: 'timestamp', header: 'TIME', color: chalk.dim },
124
+ { key: 'topic', header: 'TOPIC', color: chalk.cyan },
163
125
  { key: 'principal', header: 'PRINCIPAL', color: chalk.dim },
164
126
  ]
165
127
  return {
166
128
  columns,
167
- rows: entries.map((e) => ({
168
- seq: String(e.seq),
169
- ts: new Date(e.event.metadata.timestamp).toISOString(),
170
- topic: e.event.topic,
171
- latency: latencyOf(e),
172
- ...(showTiming ? { steps: timingOf(e) } : {}),
173
- principal: String(e.event.metadata.principal),
129
+ rows: records.map((record) => ({
130
+ sequence: String(record.sequence),
131
+ timestamp: record.timestamp,
132
+ topic: record.topic,
133
+ principal: record.principal ?? '',
174
134
  })),
175
- paths: entries.map((e) => String(e.seq)),
135
+ paths: records.map((record) => String(record.sequence)),
176
136
  }
177
137
  }
178
138
 
179
- async function fetchEventsPage(ctx: ClientContext, opts: LogsOpts): Promise<EventsPage> {
180
- const raw = await ctx.client.call(JOURNAL_FN_PATH, buildEventsParams(opts))
181
- const page = normalizePage(raw)
182
- // Strip the journal's own read ops by default (but keep nextCursor past them).
183
- const entries = opts.all
184
- ? page.entries
185
- : page.entries.filter((e) => !e.event.topic.startsWith(SELF_READ_PREFIX))
186
- return { entries, nextCursor: page.nextCursor }
187
- }
188
-
189
- function printEventLine(e: JournalEntry, showTiming = false): void {
190
- const ts = new Date(e.event.metadata.timestamp).toISOString()
191
- const lat = latencyOf(e)
192
- const steps = showTiming ? timingOf(e) : ''
139
+ function printRecord(record: JournalRecord): void {
193
140
  process.stdout.write(
194
- `${chalk.dim(String(e.seq).padStart(6))} ${chalk.dim(ts)} ${TOPIC_COLOR(e.event.topic)} ${chalk.dim(String(e.event.metadata.principal))}${lat ? ` ${LATENCY_COLOR(lat)}` : ''}${steps ? ` ${chalk.dim(`[${steps}]`)}` : ''}\n`,
141
+ `${chalk.dim(String(record.sequence).padStart(6))} ${chalk.dim(record.timestamp)} ${chalk.cyan(record.topic)} ${chalk.dim(record.principal ?? '')}\n`,
195
142
  )
196
143
  }
197
144
 
198
- async function runEvents(opts: LogsOpts): Promise<void> {
199
- if (opts.follow) return followEvents(opts)
200
-
201
- await runKernelCommand<EventsPage>({
202
- opts,
203
- label: 'Kernel events',
204
- fn: (ctx) => fetchEventsPage(ctx, opts),
205
- format: (page, fmtOpts) => {
206
- if (isMachine(fmtOpts) || fmtOpts.format) {
207
- // Machine surface: the kernel's own entries array, no projection.
208
- output(page.entries, fmtOpts)
209
- return
210
- }
211
- presentList(page.entries, fmtOpts, (entries) => eventsProjection(entries, opts.timing))
212
- if (typeof page.nextCursor === 'number') {
213
- process.stdout.write(chalk.dim(` tail: --follow (or --cursor ${page.nextCursor})\n`))
214
- }
215
- },
216
- })
217
- }
218
-
219
- /**
220
- * Client-side polling follow (ctx.client exposes no stream transport). Take the
221
- * max `seq` from each page and pass it back as `cursor` (exclusive cursor) so
222
- * the next poll only returns new entries.
223
- */
224
- async function followEvents(opts: LogsOpts): Promise<void> {
225
- await withKernelClient(opts, async (ctx) => {
226
- let cursor = opts.cursor !== undefined ? parsePositiveInt('--cursor', opts.cursor) : undefined
227
- for (;;) {
228
- const page = await fetchEventsPage(ctx, { ...opts, cursor: cursor?.toString() })
229
- for (const e of page.entries) printEventLine(e, opts.timing)
230
- if (typeof page.nextCursor === 'number') cursor = page.nextCursor
231
- await new Promise((resolve) => setTimeout(resolve, FOLLOW_INTERVAL_MS))
145
+ function acceptRecord(input: unknown, index: number): JournalRecord {
146
+ if (
147
+ !isRecord(input) ||
148
+ !Number.isSafeInteger(input.sequence) ||
149
+ typeof input.timestamp !== 'string' ||
150
+ typeof input.topic !== 'string'
151
+ ) {
152
+ throw new TypeError(`Kernel journal record ${index} is invalid`)
153
+ }
154
+ for (const key of ['principal', 'correlationId', 'causationId'] as const) {
155
+ if (input[key] !== undefined && typeof input[key] !== 'string') {
156
+ throw new TypeError(`Kernel journal record ${index}.${key} must be text`)
232
157
  }
158
+ }
159
+ return Object.freeze({
160
+ sequence: input.sequence as number,
161
+ timestamp: input.timestamp,
162
+ topic: input.topic,
163
+ payload: input.payload,
164
+ ...(input.principal === undefined ? {} : { principal: input.principal as string }),
165
+ ...(input.correlationId === undefined ? {} : { correlationId: input.correlationId as string }),
166
+ ...(input.causationId === undefined ? {} : { causationId: input.causationId as string }),
233
167
  })
234
168
  }
235
169
 
236
- // ── --service (legacy services-domain log buffer) ───────────
237
-
238
- type ServiceLogs = {
239
- name: string
240
- lines: Array<{ ts: number; level: string; line: string }>
170
+ function positiveInteger(flag: string, raw: string): number {
171
+ if (!/^\d+$/.test(raw)) throw new TypeError(`${flag} must be a positive integer`)
172
+ const value = Number.parseInt(raw, 10)
173
+ if (!Number.isSafeInteger(value) || value <= 0) {
174
+ throw new TypeError(`${flag} must be a positive integer`)
175
+ }
176
+ return value
241
177
  }
242
178
 
243
- const LEVEL_COLOR: Record<string, (s: string) => string> = {
244
- error: chalk.red,
245
- warn: chalk.yellow,
246
- access: chalk.cyan,
247
- debug: chalk.dim,
179
+ function nonEmpty(input: string | undefined): string | undefined {
180
+ const value = input?.trim()
181
+ return value ? value : undefined
248
182
  }
249
183
 
250
- /** Accept a bare service name, a `<name>.…` host, or a full https URL → the service name. */
251
- export function parseServiceName(ref: string): string {
252
- let host = ref.trim()
253
- if (host.includes('://')) {
254
- try {
255
- host = new URL(host).hostname
256
- } catch {
257
- // keep the raw value — the call will fail loud with the name
258
- }
259
- }
260
- return host.includes('.') ? (host.split('.')[0] ?? host) : host
261
- }
262
-
263
- async function runService(opts: LogsOpts): Promise<void> {
264
- const name = parseServiceName(opts.service as string)
265
- const origin = opts.servicesOrigin ?? DEFAULT_SERVICES_ORIGIN
266
- const tail = opts.tail !== undefined ? Number(opts.tail) : undefined
267
- if (tail !== undefined && (!Number.isInteger(tail) || tail <= 0)) {
268
- throw new Error(`--tail needs a positive integer, got "${opts.tail}"`)
269
- }
270
- const result = await withSpinner(`Fetching logs for ${name}`, !isMachine(opts), () =>
271
- withKernelClient(
272
- opts,
273
- async (ctx) =>
274
- (await ctx.client.call(
275
- `/${origin}/services/${name}::logs`,
276
- tail !== undefined ? { tail } : {},
277
- )) as ServiceLogs,
278
- ),
279
- )
280
- if (isMachine(opts)) {
281
- output(result, opts)
282
- return
283
- }
284
- if (result.lines.length === 0) {
285
- console.log(chalk.dim(`no log lines captured yet for ${result.name}`))
286
- return
287
- }
288
- for (const entry of result.lines) {
289
- const ts = new Date(entry.ts).toISOString()
290
- const paint = LEVEL_COLOR[entry.level] ?? ((s: string) => s)
291
- console.log(`${chalk.dim(ts)} ${paint(entry.level.padEnd(6))} ${entry.line}`)
292
- }
184
+ function isRecord(input: unknown): input is Record<string, unknown> {
185
+ return input !== null && typeof input === 'object' && !Array.isArray(input)
293
186
  }
294
187
 
295
188
  export default {
296
189
  name: 'logs',
297
- description: 'Tail the kernel event journal (or a service log buffer with --service)',
298
- options: [
299
- { flags: '--since <t>', description: 'Events at/after this time (epoch-ms or ISO-8601)' },
300
- { flags: '--until <t>', description: 'Events at/before this time (epoch-ms or ISO-8601)' },
301
- { flags: '--topic <glob>', description: 'Topic glob (e.g. op:*:failed, graph:node:**)' },
302
- { flags: '--principal <id>', description: 'Filter by the triggering identity' },
303
- { flags: '--limit <n>', description: `Max entries (default ${DEFAULT_LIMIT})` },
304
- { flags: '--cursor <n>', description: 'Start after this journal sequence number' },
305
- { flags: '--follow', description: 'Poll for new events (Ctrl-C to stop)' },
306
- { flags: '--all', description: 'Include the journal-read syscall ops (hidden by default)' },
307
- { flags: '--timing', description: 'Show the per-step dispatch breakdown (auth/authz/exec/…)' },
308
- { flags: '--service <name>', description: 'Tail a deployed service log buffer instead' },
309
- { flags: '--tail <n>', description: '[--service] lines to return (default 200, max 500)' },
310
- {
311
- flags: '--services-origin <origin>',
312
- description: `[--service] services-domain origin (default ${DEFAULT_SERVICES_ORIGIN})`,
313
- },
314
- ],
190
+ description: 'Read or follow the authorized Kernel journal',
315
191
  afterHelpText: `
316
- Default: tails the kernel event journal via ${JOURNAL_FN_PATH} on the target
317
- instance (-i <instance>). Topics use ':'-segmented globs ('*' one segment,
318
- '**' zero-or-more). Machine output (--json / pipe) emits the JournalEntry[]
319
- array; a TTY shows a SEQ/TIME/TOPIC/LATENCY/PRINCIPAL table (LATENCY is the
320
- op's durationMs, present on :completed/:failed). --timing adds a STEPS column
321
- with the per-step dispatch breakdown (auth/in/authz/resolve/inv/exec/out/fx,
322
- non-zero only). --follow polls for new entries (client-side, tailing by
323
- sequence number). The journal-read syscall's own ops are hidden unless --all.
192
+ Behavior:
193
+ Calls the public Kernel journal syscall and emits its { records, cursor }
194
+ page. Topic selection is exact or prefix-based; cursors and timestamps are
195
+ opaque strings owned by the journal backend. --follow reuses one Client Session
196
+ and advances only with the returned cursor.
324
197
 
325
- --service <name> switches to the per-instance 'services' domain log buffer
326
- (console output, 5xx accesses, uncaught exceptions). Requires the services
327
- domain installed on the target instance.
198
+ Historical event-glob lowering and the application-specific services-domain
199
+ log buffer are not part of the Kernel V2 journal contract.
328
200
 
329
201
  Examples:
330
- astrale logs -i staging
331
- astrale logs -i staging --topic 'op:*:failed' --limit 50
332
- astrale logs -i staging --topic 'op:*:completed' --timing
333
- astrale logs -i staging --since 2026-06-20T00:00:00Z --follow
334
- astrale logs --service my-notes -i staging --tail 50
202
+ $ astrale logs -i staging --limit 50
203
+ $ astrale logs --topic op:function.failed
204
+ $ astrale logs --topic-prefix op:function. --follow
335
205
  `,
206
+ options: [
207
+ { flags: '--since <timestamp>', description: 'Inclusive journal timestamp lower bound' },
208
+ { flags: '--until <timestamp>', description: 'Inclusive journal timestamp upper bound' },
209
+ { flags: '--topic <topic>', description: 'Match one exact topic' },
210
+ { flags: '--topic-prefix <prefix>', description: 'Match one topic prefix' },
211
+ { flags: '--principal <id>', description: 'Filter by triggering identity ID' },
212
+ { flags: '--limit <n>', description: `Maximum records (default: ${DEFAULT_LIMIT})` },
213
+ { flags: '--cursor <token>', description: 'Resume from an opaque journal cursor' },
214
+ { flags: '--follow', description: 'Poll using returned cursors until interrupted' },
215
+ ],
336
216
  action: async (opts: LogsOpts) => {
337
- try {
338
- if (opts.service) {
339
- await runService(opts)
340
- } else {
341
- await runEvents(opts)
342
- }
343
- } catch (e) {
344
- fatal(e)
345
- }
217
+ if (opts.follow) await follow(opts)
218
+ else await runOnce(opts)
346
219
  },
347
220
  } satisfies CommandDefinition