@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.
- package/README.md +4 -1
- package/dist/astrale.js +62968 -41240
- package/dist/public/connect-core.js +55030 -0
- package/dist/public/keys/index.js +45631 -0
- package/dist/public/paths/index.js +43990 -0
- package/dist/types/admin/instance/model.d.ts +40 -0
- package/dist/types/connect-core.d.ts +22 -0
- package/dist/types/connection/auth.d.ts +33 -0
- package/dist/types/errors.d.ts +23 -0
- package/dist/types/identity/registry.d.ts +25 -0
- package/dist/types/keys/algorithm.d.ts +6 -0
- package/dist/types/keys/credential.d.ts +21 -0
- package/dist/types/keys/index.d.ts +4 -0
- package/dist/types/keys/pair.d.ts +34 -0
- package/dist/types/lib/admin-target.d.ts +74 -0
- package/dist/types/lib/ca-fetch.d.ts +2 -0
- package/dist/types/lib/config.d.ts +71 -0
- package/dist/types/lib/format.d.ts +1 -0
- package/dist/types/lib/idp-session.d.ts +33 -0
- package/dist/types/lib/idp.d.ts +397 -0
- package/dist/types/lib/instance-target.d.ts +36 -0
- package/dist/types/lib/instance.d.ts +186 -0
- package/dist/types/lib/log.d.ts +30 -0
- package/dist/types/lib/login-flow.d.ts +49 -0
- package/dist/types/lib/meta.d.ts +36 -0
- package/dist/types/lib/output.d.ts +83 -0
- package/dist/types/lib/table.d.ts +18 -0
- package/dist/types/lib/validation.d.ts +11 -0
- package/dist/types/paths/index.d.ts +2 -0
- package/dist/types/state/exchange-credentials.d.ts +25 -0
- package/dist/types/state/files.d.ts +9 -0
- package/dist/types/state/identities.d.ts +42 -0
- package/dist/types/state/index.d.ts +8 -0
- package/dist/types/state/paths.d.ts +32 -0
- package/package.json +30 -8
- package/src/admin/.spec/api.d.ts +3 -0
- package/src/admin/.spec/architecture.md +15 -0
- package/src/admin/.spec/icon.svg +3 -0
- package/src/admin/.spec/layout.ts +6 -0
- package/src/admin/catalog/.spec/api.d.ts +65 -0
- package/src/admin/catalog/.spec/architecture.md +6 -0
- package/src/admin/catalog/.spec/icon.svg +3 -0
- package/src/admin/catalog/.spec/layout.ts +6 -0
- package/src/admin/catalog/__tests__/client.test.ts +171 -0
- package/src/admin/catalog/client.ts +214 -0
- package/src/admin/catalog/index.ts +13 -0
- package/src/admin/catalog/model.ts +42 -0
- package/src/admin/catalog/tsconfig.json +8 -0
- package/src/admin/graph/.spec/api.d.ts +28 -0
- package/src/admin/graph/.spec/architecture.md +5 -0
- package/src/admin/graph/.spec/icon.svg +3 -0
- package/src/admin/graph/.spec/layout.ts +3 -0
- package/src/admin/graph/index.ts +6 -0
- package/src/admin/graph/nodes.ts +56 -0
- package/src/admin/index.ts +2 -0
- package/src/admin/instance/.spec/api.d.ts +66 -0
- package/src/admin/instance/.spec/architecture.md +10 -0
- package/src/admin/instance/.spec/icon.svg +3 -0
- package/src/admin/instance/.spec/layout.ts +6 -0
- package/src/admin/instance/__tests__/client.test.ts +289 -0
- package/src/admin/instance/client.ts +298 -0
- package/src/admin/instance/index.ts +16 -0
- package/src/admin/instance/model.ts +65 -0
- package/src/admin/instance/tsconfig.json +8 -0
- package/src/commands/__tests__/admin-instance.test.ts +3 -36
- package/src/commands/__tests__/call.test.ts +21 -68
- package/src/commands/__tests__/domain-install-owned.test.ts +26 -27
- package/src/commands/__tests__/domain-list.test.ts +2 -6
- package/src/commands/__tests__/logs.test.ts +50 -98
- package/src/commands/__tests__/ls.test.ts +21 -17
- package/src/commands/__tests__/read-commands.test.ts +171 -101
- package/src/commands/__tests__/view.test.ts +100 -51
- package/src/commands/admin/status.ts +3 -3
- package/src/commands/admin/use.ts +29 -6
- package/src/commands/auth/login.ts +1 -1
- package/src/commands/auth/logout.ts +4 -2
- package/src/commands/auth/status.ts +2 -2
- package/src/commands/auth/token.ts +2 -2
- package/src/commands/browser.ts +1 -1
- package/src/commands/call.ts +55 -91
- package/src/commands/describe.ts +52 -147
- package/src/commands/domain/install.ts +81 -50
- package/src/commands/domain/list.ts +4 -8
- package/src/commands/domain/publish.ts +9 -31
- package/src/commands/get.ts +32 -65
- package/src/commands/identity/__tests__/register.test.ts +93 -0
- package/src/commands/identity/create.ts +2 -2
- package/src/commands/identity/delete.ts +2 -2
- package/src/commands/identity/export.ts +7 -33
- package/src/commands/identity/import.ts +21 -70
- package/src/commands/identity/list.ts +2 -2
- package/src/commands/identity/register.ts +148 -117
- package/src/commands/identity/sync.ts +2 -2
- package/src/commands/identity/unsync.ts +2 -2
- package/src/commands/identity/use.ts +2 -2
- package/src/commands/identity/whoami.ts +2 -2
- package/src/commands/idp/add.ts +1 -1
- package/src/commands/idp/list.ts +1 -1
- package/src/commands/idp/refresh.ts +1 -1
- package/src/commands/idp/remove.ts +2 -2
- package/src/commands/idp/show.ts +1 -1
- package/src/commands/instance/active.ts +2 -2
- package/src/commands/instance/bookmark.ts +8 -2
- package/src/commands/instance/create.ts +1 -1
- package/src/commands/instance/delete.ts +4 -10
- package/src/commands/instance/forget.ts +1 -1
- package/src/commands/instance/list.ts +3 -3
- package/src/commands/instance/status.ts +5 -7
- package/src/commands/instance/use.ts +3 -3
- package/src/commands/logs.ts +152 -279
- package/src/commands/ls.ts +78 -204
- package/src/commands/mutate.ts +70 -139
- package/src/commands/query.ts +100 -278
- package/src/commands/session/analyze.ts +1 -1
- package/src/commands/session/list.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/status.ts +1 -1
- package/src/commands/studio.ts +1 -1
- package/src/commands/token.ts +26 -21
- package/src/commands/update.ts +1 -1
- package/src/commands/use.ts +2 -3
- package/src/commands/view-serve.ts +1 -1
- package/src/commands/view.ts +67 -109
- package/src/connect-core.test.ts +38 -0
- package/src/connect-core.ts +42 -0
- package/src/connection/.spec/api.d.ts +111 -0
- package/src/connection/.spec/architecture.md +41 -0
- package/src/connection/.spec/capabilities/connection.ts +13 -0
- package/src/connection/.spec/examples/call.ts +7 -0
- package/src/connection/.spec/flows/command.ts +20 -0
- package/src/connection/.spec/flows/session.ts +61 -0
- package/src/connection/.spec/laws/connection.ts +181 -0
- package/src/connection/.spec/layout.ts +19 -0
- package/src/{kernel → connection}/__tests__/auth.test.ts +26 -2
- package/src/connection/__tests__/ca-fetch.test.ts +53 -0
- package/src/connection/__tests__/call.test.ts +17 -0
- package/src/connection/__tests__/credential.test.ts +109 -0
- package/src/connection/__tests__/errors.test.ts +96 -0
- package/src/connection/__tests__/exchange.test.ts +224 -0
- package/src/connection/__tests__/fixtures/localhost-cert.pem +19 -0
- package/src/connection/__tests__/fixtures/localhost-key.base64 +1 -0
- package/src/connection/__tests__/managed.test.ts +39 -0
- package/src/connection/__tests__/self.test.ts +51 -0
- package/src/connection/__tests__/session.test.ts +140 -0
- package/src/connection/__tests__/target.test.ts +93 -0
- package/src/{kernel → connection}/auth.ts +8 -8
- package/src/connection/call.ts +7 -0
- package/src/{kernel/run.ts → connection/command.ts} +22 -13
- package/src/connection/credential.ts +114 -0
- package/src/{kernel → connection}/errors.ts +32 -9
- package/src/connection/exchange.ts +274 -0
- package/src/connection/index.ts +8 -0
- package/src/connection/self.ts +120 -0
- package/src/connection/session.ts +176 -0
- package/src/connection/target.ts +111 -0
- package/src/connection/tsconfig.json +6 -0
- package/src/graph/.spec/api.d.ts +34 -0
- package/src/graph/.spec/laws/documents.ts +24 -0
- package/src/graph/.spec/laws/projection.ts +13 -0
- package/src/graph/.spec/layout.ts +6 -0
- package/src/graph/__tests__/mutation.test.ts +43 -0
- package/src/graph/__tests__/projection.test.ts +22 -0
- package/src/graph/__tests__/query.test.ts +154 -0
- package/src/graph/index.ts +3 -0
- package/src/graph/mutation.ts +18 -0
- package/src/graph/projection.ts +21 -0
- package/src/graph/query.ts +108 -0
- package/src/graph/tsconfig.json +6 -0
- package/src/identity/.history/ard/bootstrap-root-transfer.md +57 -0
- package/src/identity/.history/ard/domain-mediated-registration.md +36 -0
- package/src/identity/.spec/api.d.ts +124 -0
- package/src/identity/.spec/architecture.md +33 -0
- package/src/identity/.spec/capabilities/identity.ts +7 -0
- package/src/identity/.spec/flows/bootstrap-root.ts +79 -0
- package/src/identity/.spec/flows/domain-mediated-registration.ts +51 -0
- package/src/identity/.spec/flows/import.ts +29 -0
- package/src/identity/.spec/laws/identity.ts +56 -0
- package/src/identity/.spec/layout.ts +13 -0
- package/src/identity/.spec/schemas/identity-export-v1.schema.json +25 -0
- package/src/identity/__tests__/fixtures/registry-journey.ts +53 -0
- package/src/identity/__tests__/registration.test.ts +64 -0
- package/src/identity/__tests__/registry.test.ts +70 -0
- package/src/identity/__tests__/transfer.test.ts +198 -0
- package/src/identity/index.ts +23 -0
- package/src/identity/registration.ts +72 -0
- package/src/identity/registry.ts +172 -0
- package/src/identity/transfer.ts +187 -0
- package/src/identity/tsconfig.json +6 -0
- package/src/index.ts +1 -0
- package/src/keys/.spec/api.d.ts +74 -0
- package/src/keys/.spec/architecture.md +13 -0
- package/src/keys/.spec/laws/keys.ts +43 -0
- package/src/keys/.spec/layout.ts +6 -0
- package/src/keys/__tests__/algorithm.test.ts +39 -0
- package/src/keys/__tests__/keys.test.ts +168 -0
- package/src/keys/algorithm.ts +33 -0
- package/src/keys/credential.ts +112 -0
- package/src/keys/index.ts +14 -0
- package/src/keys/pair.ts +219 -0
- package/src/keys/tsconfig.json +6 -0
- package/src/lib/__tests__/admin-target.test.ts +67 -7
- package/src/lib/__tests__/binary.test.ts +2 -3
- package/src/lib/__tests__/command-dx.test.ts +2 -2
- package/src/lib/__tests__/config.test.ts +3 -2
- package/src/lib/__tests__/idp.test.ts +4 -6
- package/src/lib/__tests__/instance-target.test.ts +7 -4
- package/src/lib/__tests__/local-status.test.ts +1 -1
- package/src/lib/__tests__/log.test.ts +31 -0
- package/src/lib/__tests__/meta.test.ts +55 -0
- package/src/lib/__tests__/sdk-deps.test.ts +1 -1
- package/src/lib/__tests__/self.test.ts +8 -230
- package/src/lib/__tests__/use-target.test.ts +1 -1
- package/src/lib/__tests__/view-open-intent.test.ts +78 -104
- package/src/lib/__tests__/view-session.test.ts +99 -0
- package/src/lib/admin-domain.ts +39 -25
- package/src/lib/admin-instance.ts +38 -43
- package/src/lib/admin-target.ts +67 -13
- package/src/lib/binary.ts +13 -34
- package/src/lib/browser.ts +1 -1
- package/src/{kernel → lib}/ca-fetch.ts +35 -27
- package/src/lib/config.ts +1 -1
- package/src/lib/idp-session.ts +1 -1
- package/src/lib/idp.ts +3 -8
- package/src/lib/instance-target.ts +12 -8
- package/src/lib/instance.ts +34 -2
- package/src/lib/local-status.ts +2 -2
- package/src/lib/log.ts +3 -1
- package/src/lib/login-flow.ts +32 -5
- package/src/lib/meta.ts +15 -11
- package/src/lib/provision-instance.ts +4 -12
- package/src/lib/self.ts +5 -147
- package/src/lib/update.ts +1 -1
- package/src/lib/use-target.ts +1 -1
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/open-intent.ts +7 -37
- package/src/lib/view/port-allocation.ts +1 -1
- package/src/lib/view/resolve.ts +52 -45
- package/src/lib/view/server.ts +16 -33
- package/src/lib/view/session.ts +51 -22
- package/src/paths/.spec/api.d.ts +13 -0
- package/src/paths/.spec/architecture.md +5 -0
- package/src/paths/.spec/layout.ts +3 -0
- package/src/paths/index.ts +13 -0
- package/src/program/.spec/api.d.ts +42 -0
- package/src/program/.spec/architecture.md +18 -0
- package/src/program/.spec/laws/program.ts +29 -0
- package/src/program/.spec/layout.ts +14 -0
- package/src/program/__tests__/program.test.ts +359 -0
- package/src/program/build.ts +190 -0
- package/src/program/command.ts +42 -0
- package/src/program/index.ts +2 -0
- package/src/program/options.ts +40 -0
- package/src/{registry.ts → program/registry.ts} +5 -1
- package/src/program/tsconfig.json +6 -0
- package/src/setup/steps/admin.ts +18 -3
- package/src/setup/steps/instance.ts +1 -1
- package/src/state/.spec/api.d.ts +133 -0
- package/src/state/.spec/architecture.md +31 -0
- package/src/state/.spec/capabilities/state.ts +7 -0
- package/src/state/.spec/flows/identity-update.ts +22 -0
- package/src/state/.spec/laws/state.ts +73 -0
- package/src/state/.spec/layout.ts +14 -0
- package/src/state/__tests__/exchange-credentials.test.ts +188 -0
- package/src/state/__tests__/files.test.ts +113 -0
- package/src/state/__tests__/fixtures/identity-transition.ts +37 -0
- package/src/state/__tests__/identities.test.ts +142 -0
- package/src/state/__tests__/paths.test.ts +27 -0
- package/src/state/exchange-credentials.ts +219 -0
- package/src/state/files.ts +129 -0
- package/src/state/identities.ts +236 -0
- package/src/state/index.ts +29 -0
- package/src/state/paths.ts +61 -0
- package/src/state/tsconfig.json +6 -0
- package/src/telemetry/__tests__/redact.test.ts +3 -3
- package/src/telemetry/__tests__/trigger.test.ts +1 -1
- package/src/telemetry/settings.ts +1 -1
- package/src/telemetry/store.ts +1 -1
- package/src/test-utils.ts +1 -1
- package/studio/client/dist/assets/elk-api-D0cBetPW.js +1 -0
- package/studio/client/dist/assets/elk-worker.min-C9JGDOE-.js +6312 -0
- package/studio/client/dist/assets/index-BQnJ5sgd.css +1 -0
- package/studio/client/dist/assets/index-ChOr3yP0.css +1 -0
- package/studio/client/dist/assets/index-Dspir4w7.js +81 -0
- package/studio/client/dist/assets/index-bVD2KJgz.js +8 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +10 -1
- package/studio/server/introspect/extractor.ts +1 -1
- package/studio/server/state/views.test.ts +3 -7
- package/studio/server/state/views.ts +23 -30
- package/studio/tsconfig.json +23 -0
- package/tsconfig.json +1 -1
- package/viewer/dist/main.js +65 -39
- package/viewer/tsconfig.json +13 -0
- package/src/command.ts +0 -42
- package/src/commands/__tests__/help-contract.test.ts +0 -164
- package/src/kernel/__tests__/client-owned-lookup.test.ts +0 -51
- package/src/kernel/__tests__/errors.test.ts +0 -43
- package/src/kernel/__tests__/expand.test.ts +0 -123
- package/src/kernel/client.ts +0 -214
- package/src/kernel/expand.ts +0 -192
- package/src/kernel/graph.ts +0 -96
- package/src/kernel/index.ts +0 -29
- package/src/kernel/options.ts +0 -22
- package/src/kernel/types.ts +0 -14
- package/src/lib/__tests__/domain-identity.test.ts +0 -60
- package/src/lib/__tests__/fs-atomic.test.ts +0 -104
- package/src/lib/__tests__/identity.test.ts +0 -79
- package/src/lib/__tests__/keys.test.ts +0 -129
- package/src/lib/domain-identity.ts +0 -49
- package/src/lib/env.ts +0 -49
- package/src/lib/fs-atomic.ts +0 -126
- package/src/lib/identity.ts +0 -256
- package/src/lib/keys.ts +0 -294
- package/src/lib/paths.ts +0 -11
- package/src/program.ts +0 -213
- package/studio/client/dist/assets/index-BaqEuIQJ.js +0 -109
- package/studio/client/dist/assets/index-jRdExahh.css +0 -1
package/src/lib/keys.ts
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { generateKeyPair, exportJWK, importJWK, SignJWT, type JWK } from 'jose'
|
|
2
|
-
import { randomUUID } from 'node:crypto'
|
|
3
|
-
import { readFile, mkdir, access, unlink } from 'node:fs/promises'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
|
|
6
|
-
import { IdentityKeyMissingError } from '../errors'
|
|
7
|
-
import { inferAlg } from './domain-identity'
|
|
8
|
-
import { atomicWrite } from './fs-atomic'
|
|
9
|
-
import { log } from './log'
|
|
10
|
-
import { KEYS_DIR } from './paths'
|
|
11
|
-
|
|
12
|
-
const LEGACY_MANAGER_PRIVATE = 'manager.private.jwk'
|
|
13
|
-
const LEGACY_MANAGER_PUBLIC = 'manager.public.jwk'
|
|
14
|
-
|
|
15
|
-
// Default issuer/audience for the host-mode manager's own credential. The
|
|
16
|
-
// manager kernel mounts at `/host` (the reserved host slug — SPEC §5.2).
|
|
17
|
-
const DEFAULT_MANAGER_ISSUER = 'http://localhost:4400/host'
|
|
18
|
-
|
|
19
|
-
type AuthOptions = {
|
|
20
|
-
issuer?: string
|
|
21
|
-
subject?: string
|
|
22
|
-
kid?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type AuthBinding = {
|
|
26
|
-
credential: string
|
|
27
|
-
publicKey: { jwk: JWK }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type KeypairPaths = {
|
|
31
|
-
privatePath: string
|
|
32
|
-
publicPath: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Per-identity keypair paths. `manager` stays on the legacy filenames so
|
|
37
|
-
* existing installs keep working; other subjects use `<subject>.*.jwk`.
|
|
38
|
-
*/
|
|
39
|
-
export function keypairPaths(subject: string, keysDir: string = KEYS_DIR): KeypairPaths {
|
|
40
|
-
if (subject === 'manager') {
|
|
41
|
-
return {
|
|
42
|
-
privatePath: join(keysDir, LEGACY_MANAGER_PRIVATE),
|
|
43
|
-
publicPath: join(keysDir, LEGACY_MANAGER_PUBLIC),
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
privatePath: join(keysDir, `${subject}.private.jwk`),
|
|
48
|
-
publicPath: join(keysDir, `${subject}.public.jwk`),
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function fileExists(path: string): Promise<boolean> {
|
|
53
|
-
try {
|
|
54
|
-
await access(path)
|
|
55
|
-
return true
|
|
56
|
-
} catch {
|
|
57
|
-
return false
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** List identity names that have a private key on disk. */
|
|
62
|
-
export async function listIdentityKeys(keysDir: string = KEYS_DIR): Promise<string[]> {
|
|
63
|
-
try {
|
|
64
|
-
const { readdir } = await import('node:fs/promises')
|
|
65
|
-
const entries = await readdir(keysDir)
|
|
66
|
-
const names = new Set<string>()
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (entry === LEGACY_MANAGER_PRIVATE) names.add('manager')
|
|
69
|
-
else if (entry.endsWith('.private.jwk')) names.add(entry.replace(/\.private\.jwk$/, ''))
|
|
70
|
-
}
|
|
71
|
-
return Array.from(names).sort()
|
|
72
|
-
} catch {
|
|
73
|
-
return []
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Generate a fresh Ed25519 keypair as plain JWKs (not persisted).
|
|
79
|
-
*
|
|
80
|
-
* Used by the domain scaffold to stamp worker key files with a real,
|
|
81
|
-
* internally-consistent pair — defense against drift between `d` and `x`
|
|
82
|
-
* (historical template copies had a mismatched `x`, making every scaffold
|
|
83
|
-
* inherit a broken pair). Same crypto as ES256 elsewhere in the CLI but
|
|
84
|
-
* EdDSA/Ed25519 matches the worker runtime convention.
|
|
85
|
-
*/
|
|
86
|
-
export async function generateEd25519Jwk(
|
|
87
|
-
kid: string,
|
|
88
|
-
): Promise<{ privateJwk: JWK; publicJwk: JWK }> {
|
|
89
|
-
const { publicKey, privateKey } = await generateKeyPair('EdDSA', {
|
|
90
|
-
crv: 'Ed25519',
|
|
91
|
-
extractable: true,
|
|
92
|
-
})
|
|
93
|
-
const privateJwk = await exportJWK(privateKey)
|
|
94
|
-
const publicJwk = await exportJWK(publicKey)
|
|
95
|
-
privateJwk.alg = 'EdDSA'
|
|
96
|
-
publicJwk.alg = 'EdDSA'
|
|
97
|
-
privateJwk.kid = kid
|
|
98
|
-
publicJwk.kid = kid
|
|
99
|
-
return { privateJwk, publicJwk }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Generate a new keypair for `subject`, persist atomically with 0o600
|
|
104
|
-
* perms, and return the JWKs + kid. Overwrites any existing keys.
|
|
105
|
-
*/
|
|
106
|
-
export async function persistKeypair(
|
|
107
|
-
subject: string,
|
|
108
|
-
opts?: { keysDir?: string; kid?: string },
|
|
109
|
-
): Promise<{ publicJwk: JWK; privateJwk: JWK; kid: string }> {
|
|
110
|
-
const keysDir = opts?.keysDir ?? KEYS_DIR
|
|
111
|
-
const { privatePath, publicPath } = keypairPaths(subject, keysDir)
|
|
112
|
-
await mkdir(keysDir, { recursive: true })
|
|
113
|
-
|
|
114
|
-
const { publicKey, privateKey } = await generateKeyPair('ES256', { extractable: true })
|
|
115
|
-
const publicJwk = await exportJWK(publicKey)
|
|
116
|
-
const privateJwk = await exportJWK(privateKey)
|
|
117
|
-
const kid = opts?.kid ?? `${subject}-key-${randomUUID().slice(0, 8)}`
|
|
118
|
-
publicJwk.kid = kid
|
|
119
|
-
privateJwk.kid = kid
|
|
120
|
-
publicJwk.alg = 'ES256'
|
|
121
|
-
privateJwk.alg = 'ES256'
|
|
122
|
-
|
|
123
|
-
await atomicWrite(privatePath, JSON.stringify(privateJwk, null, 2))
|
|
124
|
-
await atomicWrite(publicPath, JSON.stringify(publicJwk, null, 2))
|
|
125
|
-
|
|
126
|
-
return { publicJwk, privateJwk, kid }
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** Remove a subject's keypair files. Idempotent. */
|
|
130
|
-
export async function removeKeypair(subject: string, keysDir: string = KEYS_DIR): Promise<void> {
|
|
131
|
-
const { privatePath, publicPath } = keypairPaths(subject, keysDir)
|
|
132
|
-
for (const p of [privatePath, publicPath]) {
|
|
133
|
-
try {
|
|
134
|
-
await unlink(p)
|
|
135
|
-
} catch {
|
|
136
|
-
/* ignore */
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Legacy signal: warn once per session when an unknown subject falls back
|
|
142
|
-
// to the manager key.
|
|
143
|
-
const warnedFallback = new Set<string>()
|
|
144
|
-
|
|
145
|
-
async function loadSigningMaterial(
|
|
146
|
-
subject: string,
|
|
147
|
-
keysDir: string,
|
|
148
|
-
): Promise<{ privateJwk: JWK; publicJwk: JWK; kid: string }> {
|
|
149
|
-
const { privatePath, publicPath } = keypairPaths(subject, keysDir)
|
|
150
|
-
|
|
151
|
-
if (await fileExists(privatePath)) {
|
|
152
|
-
const privateJwk = JSON.parse(await readFile(privatePath, 'utf-8')) as JWK
|
|
153
|
-
const publicJwk = JSON.parse(await readFile(publicPath, 'utf-8')) as JWK
|
|
154
|
-
return {
|
|
155
|
-
privateJwk,
|
|
156
|
-
publicJwk,
|
|
157
|
-
kid: (privateJwk.kid as string | undefined) ?? `${subject}-key`,
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (process.env.ASTRALE_STRICT_IDENTITIES === '1') {
|
|
162
|
-
throw new IdentityKeyMissingError(subject)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Manager always uses its own file. Any other subject falls through
|
|
166
|
-
// to the manager key with a one-shot warning so the migration window
|
|
167
|
-
// stays visible without being noisy.
|
|
168
|
-
if (subject === 'manager') throw new IdentityKeyMissingError(subject)
|
|
169
|
-
|
|
170
|
-
const { privatePath: mgrPrivate, publicPath: mgrPublic } = keypairPaths('manager', keysDir)
|
|
171
|
-
if (!(await fileExists(mgrPrivate))) throw new IdentityKeyMissingError(subject)
|
|
172
|
-
|
|
173
|
-
if (!warnedFallback.has(subject)) {
|
|
174
|
-
log.warn(
|
|
175
|
-
`Legacy shared-key mode for "${subject}" — run \`astrale identity create ${subject}\` to generate a dedicated key.`,
|
|
176
|
-
)
|
|
177
|
-
warnedFallback.add(subject)
|
|
178
|
-
}
|
|
179
|
-
const privateJwk = JSON.parse(await readFile(mgrPrivate, 'utf-8')) as JWK
|
|
180
|
-
const publicJwk = JSON.parse(await readFile(mgrPublic, 'utf-8')) as JWK
|
|
181
|
-
return {
|
|
182
|
-
privateJwk,
|
|
183
|
-
publicJwk,
|
|
184
|
-
kid: (privateJwk.kid as string | undefined) ?? 'manager-key',
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Sign a self-identity credential (`grant: identity/self`) from a private
|
|
190
|
-
* JWK. Shared by `persistAuth`, `loadAuth`, and `signAs` so the protected
|
|
191
|
-
* header and claim set stay identical across all three; the only thing that
|
|
192
|
-
* varies is how each caller obtains the key material and which
|
|
193
|
-
* subject/audience it stamps.
|
|
194
|
-
*/
|
|
195
|
-
async function signIdentityCredential(args: {
|
|
196
|
-
privateJwk: JWK
|
|
197
|
-
kid: string
|
|
198
|
-
issuer: string
|
|
199
|
-
subject: string
|
|
200
|
-
audience: string
|
|
201
|
-
}): Promise<string> {
|
|
202
|
-
const alg = inferAlg(args.privateJwk as Record<string, unknown>)
|
|
203
|
-
const privateKey = await importJWK(args.privateJwk, alg)
|
|
204
|
-
return new SignJWT({ grant: { v: 1, expr: { kind: 'identity', self: true } } })
|
|
205
|
-
.setProtectedHeader({ alg, kid: args.kid })
|
|
206
|
-
.setIssuer(args.issuer)
|
|
207
|
-
.setSubject(args.subject)
|
|
208
|
-
.setAudience(args.audience)
|
|
209
|
-
.sign(privateKey)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Generate a new keypair, persist to disk, and return a signed credential.
|
|
214
|
-
* Wraps the manager init path — use `persistKeypair` for bare keypair generation.
|
|
215
|
-
*/
|
|
216
|
-
export async function persistAuth(
|
|
217
|
-
keysDir: string = KEYS_DIR,
|
|
218
|
-
opts?: AuthOptions,
|
|
219
|
-
): Promise<AuthBinding> {
|
|
220
|
-
const issuer = opts?.issuer ?? DEFAULT_MANAGER_ISSUER
|
|
221
|
-
const subject = opts?.subject ?? 'manager'
|
|
222
|
-
const kid = opts?.kid ?? `${subject}-key`
|
|
223
|
-
|
|
224
|
-
const { privateJwk, publicJwk } = await persistKeypair(subject, { keysDir, kid })
|
|
225
|
-
const credential = await signIdentityCredential({
|
|
226
|
-
privateJwk,
|
|
227
|
-
kid,
|
|
228
|
-
issuer,
|
|
229
|
-
subject,
|
|
230
|
-
audience: issuer,
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
return { credential, publicKey: { jwk: publicJwk } }
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Load existing keys from disk and sign a fresh credential.
|
|
238
|
-
*/
|
|
239
|
-
export async function loadAuth(
|
|
240
|
-
keysDir: string = KEYS_DIR,
|
|
241
|
-
opts?: AuthOptions,
|
|
242
|
-
): Promise<AuthBinding> {
|
|
243
|
-
const issuer = opts?.issuer ?? DEFAULT_MANAGER_ISSUER
|
|
244
|
-
const subject = opts?.subject ?? 'manager'
|
|
245
|
-
|
|
246
|
-
const { privateJwk, publicJwk, kid } = await loadSigningMaterial(subject, keysDir)
|
|
247
|
-
const credential = await signIdentityCredential({
|
|
248
|
-
privateJwk,
|
|
249
|
-
kid,
|
|
250
|
-
issuer,
|
|
251
|
-
subject,
|
|
252
|
-
audience: issuer,
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
return { credential, publicKey: { jwk: publicJwk } }
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Load existing keys if present, otherwise generate and persist new ones.
|
|
260
|
-
*/
|
|
261
|
-
export async function resolveAuth(
|
|
262
|
-
keysDir: string = KEYS_DIR,
|
|
263
|
-
opts?: AuthOptions,
|
|
264
|
-
): Promise<AuthBinding> {
|
|
265
|
-
const subject = opts?.subject ?? 'manager'
|
|
266
|
-
const { privatePath } = keypairPaths(subject, keysDir)
|
|
267
|
-
if (await fileExists(privatePath)) {
|
|
268
|
-
return loadAuth(keysDir, opts)
|
|
269
|
-
}
|
|
270
|
-
return persistAuth(keysDir, opts)
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Sign a JWT as a specific subject. Uses the subject's own keypair when
|
|
275
|
-
* present; falls back to the manager key (with a one-shot warning) for
|
|
276
|
-
* unknown subjects until `ASTRALE_STRICT_IDENTITIES=1` is set.
|
|
277
|
-
*/
|
|
278
|
-
export async function signAs(
|
|
279
|
-
subject: string,
|
|
280
|
-
keysDir: string = KEYS_DIR,
|
|
281
|
-
opts?: { issuer?: string; audience?: string; subject?: string },
|
|
282
|
-
): Promise<string> {
|
|
283
|
-
const issuer = opts?.issuer ?? DEFAULT_MANAGER_ISSUER
|
|
284
|
-
const audience = opts?.audience ?? issuer
|
|
285
|
-
const { privateJwk, kid } = await loadSigningMaterial(subject, keysDir)
|
|
286
|
-
|
|
287
|
-
return signIdentityCredential({
|
|
288
|
-
privateJwk,
|
|
289
|
-
kid,
|
|
290
|
-
issuer,
|
|
291
|
-
subject: opts?.subject ?? subject,
|
|
292
|
-
audience,
|
|
293
|
-
})
|
|
294
|
-
}
|
package/src/lib/paths.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { paths } from './env'
|
|
2
|
-
|
|
3
|
-
export const ASTRALE_HOME = paths.home
|
|
4
|
-
export const KEYS_DIR = paths.keys
|
|
5
|
-
export const DATA_DIR = paths.data
|
|
6
|
-
export const CONFIG_PATH = paths.config
|
|
7
|
-
export const INSTALL_PATH = paths.install
|
|
8
|
-
export const IDENTITIES_PATH = paths.identities
|
|
9
|
-
export const INSTANCES_PATH = paths.instances
|
|
10
|
-
export const IDPS_PATH = paths.idps
|
|
11
|
-
export const IDP_SESSIONS_DIR = paths.idpSessionsDir
|
package/src/program.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { Command, Option } from 'commander'
|
|
2
|
-
|
|
3
|
-
import type { CommandDefinition } from './command'
|
|
4
|
-
|
|
5
|
-
import pkg from '../package.json' with { type: 'json' }
|
|
6
|
-
import { KERNEL_PASSTHROUGH_OPTIONS } from './kernel/options'
|
|
7
|
-
import { RAW_OUTPUT_OPTIONS } from './lib/output'
|
|
8
|
-
import { registerCommand, registerGroup } from './registry'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Build the fully-wired Commander program (every command + group registered)
|
|
12
|
-
* WITHOUT parsing argv. `bin/astrale.ts` is a thin shim that calls this and
|
|
13
|
-
* `.parse()`; tests import it to walk the command tree (the `--help` surface
|
|
14
|
-
* is asserted to be the source of truth it claims to be — see
|
|
15
|
-
* `commands/__tests__/help-contract.test.ts`).
|
|
16
|
-
*/
|
|
17
|
-
export async function buildProgram(): Promise<Command> {
|
|
18
|
-
const program = new Command()
|
|
19
|
-
|
|
20
|
-
program
|
|
21
|
-
.name('astrale')
|
|
22
|
-
.description('Astrale CLI — connect to existing Astrale kernels')
|
|
23
|
-
// Single source of truth = package.json (bumped by release-please together
|
|
24
|
-
// with .release-please-manifest.json). Never hand-write a version literal.
|
|
25
|
-
.version(pkg.version)
|
|
26
|
-
.showSuggestionAfterError(true)
|
|
27
|
-
.addOption(new Option('--ci', 'Machine mode: no prompts, structured errors on stderr'))
|
|
28
|
-
.addOption(new Option('--no-prompt', 'Disable interactive prompts'))
|
|
29
|
-
.addOption(
|
|
30
|
-
new Option('--offline-ok', 'Tolerate offline state for commands that can operate locally'),
|
|
31
|
-
)
|
|
32
|
-
.addOption(
|
|
33
|
-
new Option('--log-level <level>', 'Log level').choices(['debug', 'info', 'warn', 'error']),
|
|
34
|
-
)
|
|
35
|
-
.addOption(new Option('--log-format <format>', 'Log output format').choices(['text', 'json']))
|
|
36
|
-
.action(async () => {
|
|
37
|
-
// Bare `astrale` in an interactive terminal with nothing connected yet →
|
|
38
|
-
// launch the guided setup. Otherwise (configured, piped, or CI) show help.
|
|
39
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
40
|
-
const { shouldAutostartSetup } = await import('./setup/engine')
|
|
41
|
-
if (await shouldAutostartSetup()) {
|
|
42
|
-
await (await import('./commands/setup')).default.action(undefined, {})
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
program.help()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
// Options shared by every kernel-touching command (call / token / get / ls /
|
|
50
|
-
// describe / query + `domain install`). Merged onto the command's own
|
|
51
|
-
// options at the registration site so the list stays single-sourced — the
|
|
52
|
-
// command-definition files only carry their command-specific options.
|
|
53
|
-
const kernelOptions = [
|
|
54
|
-
{
|
|
55
|
-
flags: '--format <type>',
|
|
56
|
-
description: 'Output format (default: yaml in TTY, json when piped)',
|
|
57
|
-
choices: ['yaml', 'json'],
|
|
58
|
-
},
|
|
59
|
-
...RAW_OUTPUT_OPTIONS,
|
|
60
|
-
...KERNEL_PASSTHROUGH_OPTIONS,
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
const withKernelOptions = (def: CommandDefinition): CommandDefinition => ({
|
|
64
|
-
...def,
|
|
65
|
-
options: [...(def.options ?? []), ...kernelOptions],
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// Verbatim alias of `identity whoami` — defer to the command-definition
|
|
69
|
-
// module so options stay in sync.
|
|
70
|
-
const whoamiMod = await import('./commands/identity/whoami')
|
|
71
|
-
registerCommand(program, {
|
|
72
|
-
name: 'whoami',
|
|
73
|
-
description: 'Show the current default identity (alias for identity whoami)',
|
|
74
|
-
options: whoamiMod.default.options,
|
|
75
|
-
action: whoamiMod.default.action,
|
|
76
|
-
})
|
|
77
|
-
registerCommand(program, (await import('./commands/setup')).default)
|
|
78
|
-
registerCommand(program, (await import('./commands/use')).default)
|
|
79
|
-
registerCommand(program, (await import('./commands/update')).default)
|
|
80
|
-
|
|
81
|
-
// ── Graph / kernel ─────────────────────────────────────────────
|
|
82
|
-
registerCommand(program, withKernelOptions((await import('./commands/call')).default))
|
|
83
|
-
registerCommand(program, withKernelOptions((await import('./commands/token')).default))
|
|
84
|
-
registerCommand(program, withKernelOptions((await import('./commands/get')).default))
|
|
85
|
-
registerCommand(program, withKernelOptions((await import('./commands/mutate')).default))
|
|
86
|
-
registerCommand(program, withKernelOptions((await import('./commands/ls')).default))
|
|
87
|
-
registerCommand(program, withKernelOptions((await import('./commands/describe')).default))
|
|
88
|
-
registerCommand(program, withKernelOptions((await import('./commands/query')).default))
|
|
89
|
-
registerCommand(program, withKernelOptions((await import('./commands/logs')).default))
|
|
90
|
-
registerCommand(program, withKernelOptions((await import('./commands/view')).default))
|
|
91
|
-
registerCommand(program, (await import('./commands/status')).default)
|
|
92
|
-
registerCommand(program, (await import('./commands/browser')).default)
|
|
93
|
-
registerCommand(program, (await import('./commands/view-serve')).default)
|
|
94
|
-
registerCommand(program, (await import('./commands/studio')).default)
|
|
95
|
-
|
|
96
|
-
registerGroup(program, {
|
|
97
|
-
name: 'instance',
|
|
98
|
-
description: 'Manage admin-provisioned instances and local bookmarks',
|
|
99
|
-
commands: [
|
|
100
|
-
withKernelOptions((await import('./commands/instance/list')).default),
|
|
101
|
-
(await import('./commands/instance/bookmark')).default,
|
|
102
|
-
(await import('./commands/instance/forget')).default,
|
|
103
|
-
withKernelOptions((await import('./commands/instance/create')).default),
|
|
104
|
-
withKernelOptions((await import('./commands/instance/delete')).default),
|
|
105
|
-
withKernelOptions((await import('./commands/instance/status')).default),
|
|
106
|
-
(await import('./commands/instance/active')).default,
|
|
107
|
-
(await import('./commands/instance/use')).default,
|
|
108
|
-
],
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
registerGroup(program, {
|
|
112
|
-
name: 'domain',
|
|
113
|
-
description: 'List, publish, and install domains (admin catalog + per-instance install)',
|
|
114
|
-
commands: [
|
|
115
|
-
withKernelOptions((await import('./commands/domain/list')).default),
|
|
116
|
-
withKernelOptions((await import('./commands/domain/publish')).default),
|
|
117
|
-
withKernelOptions((await import('./commands/domain/install')).default),
|
|
118
|
-
],
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
registerGroup(program, {
|
|
122
|
-
name: 'admin',
|
|
123
|
-
description: 'Configure the admin kernel',
|
|
124
|
-
commands: [
|
|
125
|
-
(await import('./commands/admin/status')).default,
|
|
126
|
-
(await import('./commands/admin/use')).default,
|
|
127
|
-
],
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
registerGroup(program, {
|
|
131
|
-
name: 'identity',
|
|
132
|
-
description: 'Manage CLI identities & delegation keypairs',
|
|
133
|
-
commands: [
|
|
134
|
-
(await import('./commands/identity/create')).default,
|
|
135
|
-
(await import('./commands/identity/register')).default,
|
|
136
|
-
(await import('./commands/identity/list')).default,
|
|
137
|
-
(await import('./commands/identity/use')).default,
|
|
138
|
-
(await import('./commands/identity/whoami')).default,
|
|
139
|
-
(await import('./commands/identity/delete')).default,
|
|
140
|
-
(await import('./commands/identity/sync')).default,
|
|
141
|
-
(await import('./commands/identity/unsync')).default,
|
|
142
|
-
(await import('./commands/identity/export')).default,
|
|
143
|
-
(await import('./commands/identity/import')).default,
|
|
144
|
-
],
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
registerGroup(program, {
|
|
148
|
-
name: 'auth',
|
|
149
|
-
description: 'Authenticate with configured identity providers',
|
|
150
|
-
commands: [
|
|
151
|
-
(await import('./commands/auth/login')).default,
|
|
152
|
-
(await import('./commands/auth/token')).default,
|
|
153
|
-
(await import('./commands/auth/logout')).default,
|
|
154
|
-
(await import('./commands/auth/status')).default,
|
|
155
|
-
],
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
registerGroup(program, {
|
|
159
|
-
name: 'session',
|
|
160
|
-
description: 'Locally recorded work sessions: list and DX-analyze them',
|
|
161
|
-
commands: [
|
|
162
|
-
(await import('./commands/session/list')).default,
|
|
163
|
-
(await import('./commands/session/analyze')).default,
|
|
164
|
-
],
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
registerGroup(program, {
|
|
168
|
-
name: 'idp',
|
|
169
|
-
description: 'Manage OpenID Connect identity providers',
|
|
170
|
-
commands: [
|
|
171
|
-
(await import('./commands/idp/add')).default,
|
|
172
|
-
(await import('./commands/idp/list')).default,
|
|
173
|
-
(await import('./commands/idp/show')).default,
|
|
174
|
-
(await import('./commands/idp/refresh')).default,
|
|
175
|
-
(await import('./commands/idp/remove')).default,
|
|
176
|
-
],
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
program.addHelpText(
|
|
180
|
-
'after',
|
|
181
|
-
`
|
|
182
|
-
Command groups:
|
|
183
|
-
Getting started setup (sign in, pick an instance, equip your workspace)
|
|
184
|
-
Kernel ls, get, mutate, call, query, describe, token
|
|
185
|
-
Management admin, instance, domain, identity, auth, idp, update
|
|
186
|
-
Agent browser (drive the GUI via agent-browser)
|
|
187
|
-
Studio studio (launch the local Domain Studio GUI for a workspace)
|
|
188
|
-
|
|
189
|
-
Path syntax:
|
|
190
|
-
/domain Domain node
|
|
191
|
-
/domain/class.Name Class node (or /domain/interface.Name)
|
|
192
|
-
/domain/class.Name/method Static method — single slash
|
|
193
|
-
(interface-hosted static: /domain/interface.Name/method)
|
|
194
|
-
<nodePath>::method Instance method dispatch — double colon ::
|
|
195
|
-
@nodeId Reference a node by its UID
|
|
196
|
-
@nodeId::method Instance method on a node by UID
|
|
197
|
-
|
|
198
|
-
Examples:
|
|
199
|
-
$ astrale ls /
|
|
200
|
-
$ astrale studio
|
|
201
|
-
$ astrale admin status
|
|
202
|
-
$ astrale update --check
|
|
203
|
-
$ astrale instance bookmark staging --url https://kernel.example.com
|
|
204
|
-
$ astrale instance create my-app
|
|
205
|
-
$ astrale instance status staging
|
|
206
|
-
$ astrale token --audience shell.astrale.ai --ttl 3600
|
|
207
|
-
$ astrale query / --depth 1
|
|
208
|
-
$ astrale query --cypher 'MATCH (n) RETURN n LIMIT 5'
|
|
209
|
-
`,
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
return program
|
|
213
|
-
}
|