@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InstanceInfo } from '
|
|
1
|
+
import type { InstanceInfo } from '../admin/instance/model'
|
|
2
2
|
import type { AstraleConfig } from './config'
|
|
3
3
|
|
|
4
4
|
import { AstraleError } from '../errors'
|
|
@@ -21,13 +21,14 @@ export type InstanceTargetRequest =
|
|
|
21
21
|
| { source: 'active' }
|
|
22
22
|
| { source: 'name'; name: string }
|
|
23
23
|
| { source: 'admin' }
|
|
24
|
-
| { source: 'url'; url: string; name?: string;
|
|
24
|
+
| { source: 'url'; url: string; name?: string; kernelIssuer?: string }
|
|
25
25
|
|
|
26
26
|
export type ResolvedInstanceTarget = {
|
|
27
27
|
name?: string
|
|
28
28
|
source: 'bookmark' | 'managed' | 'admin' | 'url'
|
|
29
29
|
url: string
|
|
30
|
-
|
|
30
|
+
kernelIssuer: string
|
|
31
|
+
domainIssuer?: string
|
|
31
32
|
defaultIdentity?: string
|
|
32
33
|
caFile?: string
|
|
33
34
|
}
|
|
@@ -59,7 +60,7 @@ export async function resolveInstanceTarget(
|
|
|
59
60
|
name: request.name,
|
|
60
61
|
source: 'url',
|
|
61
62
|
url: request.url,
|
|
62
|
-
|
|
63
|
+
kernelIssuer: request.kernelIssuer ?? request.url,
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
}
|
|
@@ -102,7 +103,7 @@ async function resolveNamedInstanceTarget(
|
|
|
102
103
|
name: managed.slug,
|
|
103
104
|
source: 'managed',
|
|
104
105
|
url,
|
|
105
|
-
|
|
106
|
+
kernelIssuer: url,
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -119,7 +120,8 @@ async function resolveBookmarkedInstanceTarget(
|
|
|
119
120
|
name: key,
|
|
120
121
|
source: 'bookmark',
|
|
121
122
|
url,
|
|
122
|
-
|
|
123
|
+
kernelIssuer: entry.issuer ? normalizeInstanceKernelUrl(entry.issuer) : url,
|
|
124
|
+
domainIssuer: entry.domainIssuer,
|
|
123
125
|
defaultIdentity: entry.defaultIdentity,
|
|
124
126
|
caFile: entry.caFile,
|
|
125
127
|
}
|
|
@@ -130,7 +132,8 @@ async function resolveBookmarkedInstanceTarget(
|
|
|
130
132
|
name: resolved.name,
|
|
131
133
|
source: 'bookmark',
|
|
132
134
|
url: resolved.url,
|
|
133
|
-
|
|
135
|
+
kernelIssuer: resolved.issuer ?? resolved.url,
|
|
136
|
+
domainIssuer: resolved.domainIssuer,
|
|
134
137
|
defaultIdentity: resolved.defaultIdentity,
|
|
135
138
|
caFile: resolved.caFile,
|
|
136
139
|
}
|
|
@@ -163,7 +166,8 @@ export function adminTargetToInstance(target: ResolvedAdminTarget): ResolvedInst
|
|
|
163
166
|
name: target.registrationSlug,
|
|
164
167
|
source: 'admin',
|
|
165
168
|
url: target.url,
|
|
166
|
-
|
|
169
|
+
kernelIssuer: target.kernelIssuer,
|
|
170
|
+
...(target.domainIssuer === undefined ? {} : { domainIssuer: target.domainIssuer }),
|
|
167
171
|
defaultIdentity: target.defaultIdentity,
|
|
168
172
|
...(target.caFile ? { caFile: target.caFile } : {}),
|
|
169
173
|
}
|
package/src/lib/instance.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { z } from 'zod'
|
|
|
5
5
|
import type { AstraleConfig } from './config'
|
|
6
6
|
|
|
7
7
|
import { AstraleError, IdentifierCollisionError, ReservedSlugError } from '../errors'
|
|
8
|
+
import { ExchangeCredentialCache, INSTANCES_PATH } from '../state/index'
|
|
8
9
|
import { log } from './log'
|
|
9
|
-
import { INSTANCES_PATH } from './paths'
|
|
10
10
|
import {
|
|
11
11
|
RESERVED_SLUGS,
|
|
12
12
|
RegistryModeSchema,
|
|
@@ -21,6 +21,7 @@ export type InstanceKind = z.infer<typeof InstanceKindSchema>
|
|
|
21
21
|
export const InstanceEntrySchema = z.object({
|
|
22
22
|
url: z.string().optional(),
|
|
23
23
|
issuer: z.string().optional(),
|
|
24
|
+
domainIssuer: z.string().optional(),
|
|
24
25
|
createdAt: z.string().optional(),
|
|
25
26
|
slug: z.string().optional(),
|
|
26
27
|
name: z.string().optional(),
|
|
@@ -48,6 +49,7 @@ export type AddInstanceOpts = {
|
|
|
48
49
|
kind?: InstanceKind
|
|
49
50
|
mode?: RegistryMode
|
|
50
51
|
issuer?: string
|
|
52
|
+
domainIssuer?: string
|
|
51
53
|
defaultIdentity?: string
|
|
52
54
|
caFile?: string
|
|
53
55
|
organizationId?: string
|
|
@@ -58,6 +60,7 @@ export type ResolvedInstance = {
|
|
|
58
60
|
kind: InstanceKind | 'managed'
|
|
59
61
|
url: string
|
|
60
62
|
issuer?: string
|
|
63
|
+
domainIssuer?: string
|
|
61
64
|
createdAt?: string
|
|
62
65
|
defaultIdentity?: string
|
|
63
66
|
caFile?: string
|
|
@@ -88,6 +91,7 @@ export function sanitizeStore(store: InstanceStore): { store: InstanceStore; cha
|
|
|
88
91
|
...entry,
|
|
89
92
|
url: normalizedUrl,
|
|
90
93
|
issuer: normalizedIssuer,
|
|
94
|
+
domainIssuer: entry.domainIssuer,
|
|
91
95
|
kind: 'bookmark',
|
|
92
96
|
}
|
|
93
97
|
// VALUE comparison only. The old `next !== entry` (object identity) was
|
|
@@ -113,6 +117,15 @@ export function sanitizeStore(store: InstanceStore): { store: InstanceStore; cha
|
|
|
113
117
|
|
|
114
118
|
let instancesMemo: InstanceStore | null = null
|
|
115
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Invalidate the one-process bookmark snapshot. Long-lived compatibility
|
|
122
|
+
* consumers call this before a read so CLI writes made by another process are
|
|
123
|
+
* observable; one-shot commands normally never need it.
|
|
124
|
+
*/
|
|
125
|
+
export function resetInstancesMemo(): void {
|
|
126
|
+
instancesMemo = null
|
|
127
|
+
}
|
|
128
|
+
|
|
116
129
|
export async function readInstances(
|
|
117
130
|
_config?: AstraleConfig,
|
|
118
131
|
opts: { persist?: boolean } = {},
|
|
@@ -204,6 +217,7 @@ export async function addInstance(key: string, opts: AddInstanceOpts = {}): Prom
|
|
|
204
217
|
...opts,
|
|
205
218
|
url: normalizedUrl,
|
|
206
219
|
issuer: opts.issuer ? normalizeInstanceKernelUrl(opts.issuer) : opts.issuer,
|
|
220
|
+
domainIssuer: opts.domainIssuer ? normalizeIssuerUrl(opts.domainIssuer) : opts.domainIssuer,
|
|
207
221
|
kind: 'bookmark',
|
|
208
222
|
createdAt: new Date().toISOString(),
|
|
209
223
|
}
|
|
@@ -228,11 +242,15 @@ export async function upsertInstance(
|
|
|
228
242
|
|
|
229
243
|
const existing = store.instances[key]
|
|
230
244
|
const normalizedIssuer = opts.issuer ? normalizeInstanceKernelUrl(opts.issuer) : undefined
|
|
245
|
+
const normalizedDomainIssuer = opts.domainIssuer
|
|
246
|
+
? normalizeIssuerUrl(opts.domainIssuer)
|
|
247
|
+
: undefined
|
|
231
248
|
const entry: InstanceEntry = {
|
|
232
249
|
...existing,
|
|
233
250
|
...definedEntry(opts),
|
|
234
251
|
url: normalizedUrl,
|
|
235
252
|
...(normalizedIssuer ? { issuer: normalizedIssuer } : {}),
|
|
253
|
+
...(normalizedDomainIssuer ? { domainIssuer: normalizedDomainIssuer } : {}),
|
|
236
254
|
kind: 'bookmark',
|
|
237
255
|
createdAt: existing?.createdAt ?? new Date().toISOString(),
|
|
238
256
|
}
|
|
@@ -273,10 +291,12 @@ export async function upsertManagedBookmark(
|
|
|
273
291
|
|
|
274
292
|
export async function removeInstance(key: string): Promise<void> {
|
|
275
293
|
const store = await readInstances()
|
|
276
|
-
|
|
294
|
+
const removed = store.instances[key]
|
|
295
|
+
if (!removed) throw new Error(`Instance "${key}" not found`)
|
|
277
296
|
delete store.instances[key]
|
|
278
297
|
if (store.active === key) store.active = Object.keys(store.instances)[0] ?? ''
|
|
279
298
|
await writeInstances(store)
|
|
299
|
+
await new ExchangeCredentialCache().deleteKernel(removed.issuer ?? removed.url!)
|
|
280
300
|
}
|
|
281
301
|
|
|
282
302
|
export async function setActive(identifier: string): Promise<string> {
|
|
@@ -328,6 +348,7 @@ export async function resolveInstance(
|
|
|
328
348
|
kind: 'bookmark',
|
|
329
349
|
url: normalizeInstanceKernelUrl(entry.url),
|
|
330
350
|
issuer: entry.issuer ? normalizeInstanceKernelUrl(entry.issuer) : entry.issuer,
|
|
351
|
+
domainIssuer: entry.domainIssuer,
|
|
331
352
|
createdAt: entry.createdAt,
|
|
332
353
|
defaultIdentity: entry.defaultIdentity,
|
|
333
354
|
caFile: entry.caFile,
|
|
@@ -380,6 +401,17 @@ export function normalizeInstanceKernelUrl(url: string): string {
|
|
|
380
401
|
return parsed.toString().replace(/\/$/, '')
|
|
381
402
|
}
|
|
382
403
|
|
|
404
|
+
export function normalizeIssuerUrl(url: string): string {
|
|
405
|
+
try {
|
|
406
|
+
const parsed = new URL(url)
|
|
407
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) return url
|
|
408
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/u, '') || '/'
|
|
409
|
+
return parsed.toString().replace(/\/$/u, '')
|
|
410
|
+
} catch {
|
|
411
|
+
return url
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
383
415
|
function isRegionRoutedInstanceRoot(url: URL): boolean {
|
|
384
416
|
if (url.pathname !== '' && url.pathname !== '/') return false
|
|
385
417
|
if (url.search || url.hash) return false
|
package/src/lib/local-status.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { decodeJwt } from 'jose'
|
|
|
2
2
|
|
|
3
3
|
import type { AstraleConfig } from './config'
|
|
4
4
|
|
|
5
|
+
import { readIdentities, type IdentityStore } from '../identity/index'
|
|
5
6
|
import { resolveAdminTargetFromStore } from './admin-target'
|
|
6
7
|
import { DEFAULT_CONFIG, readConfig } from './config'
|
|
7
|
-
import { readIdentities, type IdentityStore } from './identity'
|
|
8
8
|
import { isSessionExpired, readIdpSession, type IdpSession } from './idp'
|
|
9
9
|
import { readInstances, type InstanceStore } from './instance'
|
|
10
10
|
|
|
@@ -123,7 +123,7 @@ function buildAdminStatus(config: AstraleConfig, instances: InstanceStore): Loca
|
|
|
123
123
|
return {
|
|
124
124
|
name: target.name,
|
|
125
125
|
url: target.url,
|
|
126
|
-
issuer: target.
|
|
126
|
+
issuer: target.kernelIssuer,
|
|
127
127
|
source: target.source,
|
|
128
128
|
configured: target.configured,
|
|
129
129
|
}
|
package/src/lib/log.ts
CHANGED
|
@@ -8,7 +8,9 @@ import { isMachine, type RawOutputOpts } from './output'
|
|
|
8
8
|
export const log = {
|
|
9
9
|
info: (msg: string) => console.log(chalk.blue('ℹ'), msg),
|
|
10
10
|
success: (msg: string) => console.log(chalk.green('✔'), msg),
|
|
11
|
-
|
|
11
|
+
// Diagnostics never share stdout with command results. In particular,
|
|
12
|
+
// --json/--raw consumers must receive exactly one parseable value there.
|
|
13
|
+
warn: (msg: string) => console.error(chalk.yellow('⚠'), msg),
|
|
12
14
|
error: (msg: string) => console.error(chalk.red('✖'), msg),
|
|
13
15
|
step: (msg: string) => console.log(chalk.cyan('→'), msg),
|
|
14
16
|
dim: (msg: string) => console.log(chalk.dim(msg)),
|
package/src/lib/login-flow.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { upsertIdpIdentity } from '
|
|
1
|
+
import { upsertIdpIdentity } from '../identity/registry'
|
|
2
2
|
import {
|
|
3
3
|
decodeTokenClaims,
|
|
4
4
|
exchangeAuthorizationCode,
|
|
@@ -42,6 +42,19 @@ export type LoginFlowOpts = {
|
|
|
42
42
|
codeVerifier?: string
|
|
43
43
|
/** Switch the default identity to the one we just logged in (default true). */
|
|
44
44
|
use?: boolean
|
|
45
|
+
/**
|
|
46
|
+
* Optional device-flow presentation boundary. When supplied, the caller
|
|
47
|
+
* receives the verification coordinates instead of terminal log output.
|
|
48
|
+
*/
|
|
49
|
+
onVerification?: (event: DeviceVerification) => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type DeviceVerification = {
|
|
53
|
+
verificationUri: string
|
|
54
|
+
verificationUriComplete?: string
|
|
55
|
+
userCode?: string
|
|
56
|
+
expiresIn?: number
|
|
57
|
+
message?: string
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
export type LoginResult = {
|
|
@@ -148,10 +161,24 @@ async function obtainToken(
|
|
|
148
161
|
scope,
|
|
149
162
|
audience: opts.audience,
|
|
150
163
|
})
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
const verificationUri = device.verification_uri ?? device.verification_uri_complete
|
|
165
|
+
if (!verificationUri) {
|
|
166
|
+
throw new Error('IdP device authorization response did not include a verification URL')
|
|
167
|
+
}
|
|
168
|
+
const verification: DeviceVerification = {
|
|
169
|
+
verificationUri,
|
|
170
|
+
verificationUriComplete: device.verification_uri_complete,
|
|
171
|
+
userCode: device.user_code,
|
|
172
|
+
expiresIn: device.expires_in,
|
|
173
|
+
message: device.message,
|
|
174
|
+
}
|
|
175
|
+
if (opts.onVerification) opts.onVerification(verification)
|
|
176
|
+
else {
|
|
177
|
+
if (device.verification_uri_complete) log.info(`Open: ${device.verification_uri_complete}`)
|
|
178
|
+
else if (device.verification_uri) log.info(`Open: ${device.verification_uri}`)
|
|
179
|
+
if (device.user_code) log.info(`Code: ${device.user_code}`)
|
|
180
|
+
if (device.message) log.dim(` ${device.message}`)
|
|
181
|
+
}
|
|
155
182
|
|
|
156
183
|
return pollDeviceToken({
|
|
157
184
|
idp,
|
package/src/lib/meta.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
import { IssuerUnreachableError } from '../errors'
|
|
14
14
|
|
|
15
|
+
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
|
16
|
+
|
|
15
17
|
export type DiscoveryDocument = {
|
|
16
18
|
/** OIDC issuer URL. */
|
|
17
19
|
issuer: string
|
|
@@ -22,7 +24,7 @@ export type DiscoveryDocument = {
|
|
|
22
24
|
export async function fetchDiscovery(
|
|
23
25
|
url: string,
|
|
24
26
|
timeoutMs = 5_000,
|
|
25
|
-
fetchImpl:
|
|
27
|
+
fetchImpl: FetchLike = globalThis.fetch,
|
|
26
28
|
): Promise<DiscoveryDocument> {
|
|
27
29
|
const discoveryUrl = url.replace(/\/+$/, '') + '/.well-known/openid-configuration'
|
|
28
30
|
try {
|
|
@@ -41,7 +43,7 @@ export async function fetchDiscovery(
|
|
|
41
43
|
export async function fetchJwks(
|
|
42
44
|
jwksUri: string,
|
|
43
45
|
timeoutMs = 5_000,
|
|
44
|
-
fetchImpl:
|
|
46
|
+
fetchImpl: FetchLike = globalThis.fetch,
|
|
45
47
|
): Promise<{ keys: Array<{ kid?: string }> }> {
|
|
46
48
|
try {
|
|
47
49
|
const r = await fetchImpl(jwksUri, { signal: AbortSignal.timeout(timeoutMs) })
|
|
@@ -67,20 +69,22 @@ export async function fetchOrgHint(url: string, timeoutMs = 5_000): Promise<stri
|
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
/**
|
|
71
|
-
* Verify the issuer at `url` publishes OIDC discovery and a non-empty JWKS.
|
|
72
|
-
* `issuerOverride` forces a specific expected issuer when discovery URL and
|
|
73
|
-
* declared issuer differ.
|
|
74
|
-
*/
|
|
72
|
+
/** Verify the pinned issuer publishes self-consistent OIDC discovery and a non-empty JWKS. */
|
|
75
73
|
export async function checkIssuerReachability(
|
|
76
74
|
url: string,
|
|
77
75
|
issuerOverride?: string,
|
|
78
|
-
fetchImpl?:
|
|
76
|
+
fetchImpl?: FetchLike,
|
|
79
77
|
): Promise<{ issuer: string; keys: Array<{ kid?: string }> }> {
|
|
80
|
-
const
|
|
81
|
-
const
|
|
78
|
+
const discoveryBase = issuerOverride ?? url
|
|
79
|
+
const discovery = await fetchDiscovery(discoveryBase, 5_000, fetchImpl)
|
|
80
|
+
if (issuerOverride !== undefined && discovery.issuer !== issuerOverride) {
|
|
81
|
+
throw new IssuerUnreachableError(
|
|
82
|
+
`${discoveryBase.replace(/\/+$/, '')}/.well-known/openid-configuration`,
|
|
83
|
+
`discovery declared issuer "${discovery.issuer}" instead of "${issuerOverride}"`,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
82
86
|
const jwks = await fetchJwks(discovery.jwksUri, 5_000, fetchImpl)
|
|
83
87
|
if (jwks.keys.length === 0)
|
|
84
88
|
throw new IssuerUnreachableError(discovery.jwksUri, 'no keys published')
|
|
85
|
-
return { issuer, keys: jwks.keys }
|
|
89
|
+
return { issuer: discovery.issuer, keys: jwks.keys }
|
|
86
90
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
|
|
3
|
-
import type { KernelCommandOpts } from '../
|
|
3
|
+
import type { KernelCommandOpts } from '../connection'
|
|
4
4
|
import type { AdminTargetCommandOpts } from './admin-target'
|
|
5
5
|
|
|
6
6
|
import { AuthError } from '../errors'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { readIdentities, type IdentityStore } from './identity'
|
|
7
|
+
import { readIdentities, type IdentityStore } from '../identity/index'
|
|
8
|
+
import { createOwnedInstance } from './admin-instance'
|
|
10
9
|
import { setActive, upsertManagedBookmark } from './instance'
|
|
11
10
|
import { withSpinner } from './log'
|
|
12
11
|
import { isMachine } from './output'
|
|
@@ -71,14 +70,7 @@ export async function provisionInstance(
|
|
|
71
70
|
`Provisioning instance ${slug}`,
|
|
72
71
|
!machine,
|
|
73
72
|
async () => {
|
|
74
|
-
const created = await
|
|
75
|
-
createOpts,
|
|
76
|
-
async (ctx) =>
|
|
77
|
-
(await ctx.client.call(adminInstanceMethod('alphaCreate'), {
|
|
78
|
-
slug,
|
|
79
|
-
...(hostId ? { host_id: hostId } : {}),
|
|
80
|
-
})) as { url: string; organizationId?: string },
|
|
81
|
-
)
|
|
73
|
+
const created = await createOwnedInstance(createOpts, slug, hostId)
|
|
82
74
|
try {
|
|
83
75
|
// Org id from the create response — authoritative for token scoping
|
|
84
76
|
// (the router's /auth/org is eventually consistent).
|
package/src/lib/self.ts
CHANGED
|
@@ -1,164 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@self`
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Kernel-side, `Identity::registerIdentity` writes `sub = String(self.id)`
|
|
6
|
-
* (kernel/runtime/syscalls/identity/index.ts), so for any properly-registered
|
|
7
|
-
* identity the JWT `sub` IS the calling node's id on the target kernel.
|
|
8
|
-
* Delegation tokens follow the same shape (outer-envelope `sub` = identityId).
|
|
9
|
-
*
|
|
10
|
-
* The expansion is fail-loud: when no `sub` is resolvable, return a typed
|
|
11
|
-
* refusal carrying the exact next-step command. Never silently fall back to
|
|
12
|
-
* a label-ish `identity.subject` like "manager" or "alice".
|
|
2
|
+
* Lexical `@self` helpers. Effective-principal resolution is deliberately not
|
|
3
|
+
* local: connection/self.ts asks the authenticated target Kernel through
|
|
4
|
+
* Identity.whoami before replacing any reference.
|
|
13
5
|
*/
|
|
14
|
-
import { decodeJwt } from 'jose'
|
|
15
|
-
|
|
16
|
-
import type { Identity } from './identity'
|
|
17
|
-
|
|
18
|
-
/** Inputs to `resolveSelfNodeId`. All local — no kernel round-trip. */
|
|
19
|
-
export type SelfResolverContext = {
|
|
20
|
-
/** Identity that will sign this call (default identity or the one from `--as`). */
|
|
21
|
-
identity?: Identity & { name: string }
|
|
22
|
-
/** Resolved instance slug, if any (absent when `--url` is used without `-i`). */
|
|
23
|
-
instanceSlug?: string
|
|
24
|
-
/** Raw `--creds` JWT, if the user provided one. */
|
|
25
|
-
credsJwt?: string
|
|
26
|
-
/** True when `auth.ts` will sign as the instance itself (per-instance keypair, not user identity). */
|
|
27
|
-
instanceSigned: boolean
|
|
28
|
-
/** `sub` decoded from a cached IdP token for source=idp identities, when available. */
|
|
29
|
-
idpSubject?: string
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export type SelfRefusal =
|
|
33
|
-
| { reason: 'manager' }
|
|
34
|
-
| { reason: 'no-registration'; identityName: string; instanceSlug: string }
|
|
35
|
-
| { reason: 'instance-signed'; instanceSlug: string }
|
|
36
|
-
| { reason: 'url-no-slug' }
|
|
37
|
-
| { reason: 'creds-no-sub' }
|
|
38
|
-
| { reason: 'idp-no-sub'; identityName: string }
|
|
39
|
-
|
|
40
|
-
export type SelfResolution = { id: string } | SelfRefusal
|
|
41
6
|
|
|
42
7
|
// Anchors:
|
|
43
8
|
// left: start-of-string OR right after `=` (param-value head)
|
|
44
9
|
// right: end-of-string OR `::` (instance method) OR `/` (path navigation)
|
|
45
|
-
// The `/` lookahead is required so `@self/functions` etc. expand — the
|
|
46
|
-
// `astrale ls @self/functions` form is documented in SKILL.md and the
|
|
47
|
-
// sandbox prefix. Without it the regex silently no-ops on those inputs.
|
|
48
10
|
const SELF_RE = /(?:^|(?<=[=]))@self(?=$|::|\/)/g
|
|
49
11
|
|
|
50
|
-
/**
|
|
51
|
-
export function resolveSelfNodeId(ctx: SelfResolverContext): SelfResolution {
|
|
52
|
-
// 1. `--creds <jwt>` — bypasses every identity lookup. Read the sub claim.
|
|
53
|
-
if (ctx.credsJwt) {
|
|
54
|
-
try {
|
|
55
|
-
const sub = decodeJwt(ctx.credsJwt).sub
|
|
56
|
-
// `.trim().length` rejects whitespace-only subs (`' '`, `'\t'`).
|
|
57
|
-
// A hand-crafted JWT with `sub: ' '` previously round-tripped into
|
|
58
|
-
// `@ ::method`, producing a malformed kernel call instead of the
|
|
59
|
-
// typed `creds-no-sub` refusal.
|
|
60
|
-
if (typeof sub === 'string' && sub.trim().length > 0) return { id: sub }
|
|
61
|
-
} catch {
|
|
62
|
-
// fall through to creds-no-sub
|
|
63
|
-
}
|
|
64
|
-
return { reason: 'creds-no-sub' }
|
|
65
|
-
}
|
|
66
|
-
// 2. Instance-signed: no user identity context — `@self` is undefined.
|
|
67
|
-
if (ctx.instanceSigned) {
|
|
68
|
-
return { reason: 'instance-signed', instanceSlug: ctx.instanceSlug ?? 'unknown' }
|
|
69
|
-
}
|
|
70
|
-
// 3. IdP-backed identities: the provider token's `sub` is the IdP USER id
|
|
71
|
-
// (e.g. `user_01K…`), NEVER a graph node id — expanding to it always
|
|
72
|
-
// produced NOT_FOUND. Use a cached registration when one exists (a future
|
|
73
|
-
// `whoami`-backed cache can populate it); otherwise refuse with the recipe.
|
|
74
|
-
if ((ctx.identity?.source ?? 'key') === 'idp') {
|
|
75
|
-
const cached = ctx.instanceSlug
|
|
76
|
-
? ctx.identity?.registrations?.[ctx.instanceSlug]?.sub
|
|
77
|
-
: undefined
|
|
78
|
-
if (cached) return { id: cached }
|
|
79
|
-
return { reason: 'idp-no-sub', identityName: ctx.identity?.name ?? '(unknown)' }
|
|
80
|
-
}
|
|
81
|
-
// 4. `--url` without `-i`: no slug to look up registration against.
|
|
82
|
-
if (!ctx.instanceSlug) return { reason: 'url-no-slug' }
|
|
83
|
-
// 5. Bootstrap `manager` identity has no graph node by construction.
|
|
84
|
-
if (
|
|
85
|
-
ctx.identity?.name === 'manager' &&
|
|
86
|
-
(!ctx.identity.registrations || Object.keys(ctx.identity.registrations).length === 0)
|
|
87
|
-
) {
|
|
88
|
-
return { reason: 'manager' }
|
|
89
|
-
}
|
|
90
|
-
// 6. Default path: read the registration entry for the resolved slug.
|
|
91
|
-
// `sub` IS the node id by construction for entries written by
|
|
92
|
-
// `registerIdentity` (kernel/runtime/syscalls/identity/index.ts:207).
|
|
93
|
-
const id = ctx.identity?.registrations?.[ctx.instanceSlug]?.sub
|
|
94
|
-
if (!id) {
|
|
95
|
-
return {
|
|
96
|
-
reason: 'no-registration',
|
|
97
|
-
identityName: ctx.identity?.name ?? '(unknown)',
|
|
98
|
-
instanceSlug: ctx.instanceSlug,
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return { id }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Cheap pre-check before invoking the resolver — skip when no `@self` appears. */
|
|
12
|
+
/** Cheap pre-check before invoking authenticated principal resolution. */
|
|
105
13
|
export function containsSelfRef(input: string): boolean {
|
|
106
14
|
SELF_RE.lastIndex = 0
|
|
107
15
|
return SELF_RE.test(input)
|
|
108
16
|
}
|
|
109
17
|
|
|
110
|
-
/**
|
|
111
|
-
* Replace every `@self` token (path head or `=@self`) with `@<selfId>`.
|
|
112
|
-
*
|
|
113
|
-
* Uses the function-form replacement so a `selfId` containing `$&`, `$$`,
|
|
114
|
-
* `$<n>`, etc. is not interpreted as a `String.replace` substitution
|
|
115
|
-
* pattern. A delegation-token `sub` claim or future opaque kernel id can
|
|
116
|
-
* legitimately carry `$`.
|
|
117
|
-
*/
|
|
18
|
+
/** Replace every admitted `@self` token with one authenticated NodeId. */
|
|
118
19
|
export function expandSelfReferences(input: string, selfId: string): string {
|
|
119
20
|
const replacement = `@${selfId}`
|
|
120
21
|
return input.replace(SELF_RE, () => replacement)
|
|
121
22
|
}
|
|
122
|
-
|
|
123
|
-
/** Build a human-facing error carrying the typed refusal as metadata. */
|
|
124
|
-
export function selfRefusalError(r: SelfRefusal): Error {
|
|
125
|
-
const e = new Error(refusalMessage(r))
|
|
126
|
-
e.name = 'SelfRefusalError'
|
|
127
|
-
;(e as Error & { selfRefusal: SelfRefusal }).selfRefusal = r
|
|
128
|
-
return e
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function refusalMessage(r: SelfRefusal): string {
|
|
132
|
-
switch (r.reason) {
|
|
133
|
-
case 'manager':
|
|
134
|
-
return [
|
|
135
|
-
"`@self` not available: you're signed in as the bootstrap `manager` identity, which has no graph node.",
|
|
136
|
-
'Run `astrale identity create <name>` then `astrale identity register <name>` to enable `@self`.',
|
|
137
|
-
].join('\n ')
|
|
138
|
-
case 'no-registration':
|
|
139
|
-
return [
|
|
140
|
-
`\`@self\` not available: identity "${r.identityName}" has no registration on instance "${r.instanceSlug}".`,
|
|
141
|
-
`Run \`astrale identity register ${r.identityName} -i ${r.instanceSlug}\`.`,
|
|
142
|
-
].join('\n ')
|
|
143
|
-
case 'instance-signed':
|
|
144
|
-
return [
|
|
145
|
-
`\`@self\` not available: this call signs as instance "${r.instanceSlug}" itself, not a user identity.`,
|
|
146
|
-
'Use `--as <name>` to sign as a registered identity, or pass a literal `@<nodeId>`.',
|
|
147
|
-
].join('\n ')
|
|
148
|
-
case 'url-no-slug':
|
|
149
|
-
return [
|
|
150
|
-
'`@self` needs an instance slug to look up the registration.',
|
|
151
|
-
'Add `-i <slug>`, or if this URL maps to a bookmark, use that slug instead of `--url`.',
|
|
152
|
-
].join('\n ')
|
|
153
|
-
case 'creds-no-sub':
|
|
154
|
-
return [
|
|
155
|
-
'`@self` not available: the `--creds` JWT has no usable `sub` claim.',
|
|
156
|
-
'Pass a literal `@<nodeId>` instead.',
|
|
157
|
-
].join('\n ')
|
|
158
|
-
case 'idp-no-sub':
|
|
159
|
-
return [
|
|
160
|
-
`\`@self\` could not be resolved for IdP identity "${r.identityName}": no cached registration, and the automatic whoami lookup did not return a node id (offline, auth failure, or unprovisioned identity).`,
|
|
161
|
-
'The CLI resolves `@self` through the bound auth.whoami() flow; fix auth/registration and rerun, or pass a literal `@<nodeId>`.',
|
|
162
|
-
].join('\n ')
|
|
163
|
-
}
|
|
164
|
-
}
|
package/src/lib/update.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, join } from 'node:path'
|
|
|
5
5
|
import { z } from 'zod'
|
|
6
6
|
|
|
7
7
|
import { AstraleError } from '../errors'
|
|
8
|
-
import { INSTALL_PATH } from '
|
|
8
|
+
import { INSTALL_PATH } from '../state/index'
|
|
9
9
|
import { run } from './proc'
|
|
10
10
|
|
|
11
11
|
const DEFAULT_REPO = 'astrale-os/cli'
|
package/src/lib/use-target.ts
CHANGED
package/src/lib/validation.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDnsLabel } from '@astrale-os/
|
|
1
|
+
import { isDnsLabel } from '@astrale-os/sdk/domain'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
import { ReservedSlugError } from '../errors'
|
|
@@ -21,7 +21,7 @@ export function validateName(name: string, entity: string): void {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export function validateSlug(slug: string): void {
|
|
24
|
-
// Canonical DNS-label rule (`@astrale-os/
|
|
24
|
+
// Canonical DNS-label rule (`@astrale-os/sdk/domain`): a slug becomes a
|
|
25
25
|
// hostname label, so reject anything not DNS-safe (§4.7).
|
|
26
26
|
if (!slug || !isDnsLabel(slug)) {
|
|
27
27
|
throw new Error(
|
|
@@ -3,8 +3,8 @@ import type { IntentMessage, MountedWindow, ResolvedView, Shell } from '@astrale
|
|
|
3
3
|
export interface OpenIntentHost {
|
|
4
4
|
current(): MountedWindow | null
|
|
5
5
|
setCurrent(window: MountedWindow): void
|
|
6
|
-
mount(view: ResolvedView
|
|
7
|
-
opened(view: ResolvedView
|
|
6
|
+
mount(view: ResolvedView): Promise<MountedWindow>
|
|
7
|
+
opened(view: ResolvedView): void
|
|
8
8
|
failed(error: unknown): void
|
|
9
9
|
reply(message: IntentMessage<'open'>, windowId: string): void
|
|
10
10
|
reject(message: IntentMessage<'open'>, error: unknown): void
|
|
@@ -29,10 +29,10 @@ export async function handleOpenIntent(
|
|
|
29
29
|
try {
|
|
30
30
|
const selected = selectResolvedView(await shell.views.resolve(nodeId), viewId)
|
|
31
31
|
const previous = host.current()
|
|
32
|
-
const next = await host.mount(selected
|
|
32
|
+
const next = await host.mount(selected)
|
|
33
33
|
|
|
34
34
|
host.setCurrent(next)
|
|
35
|
-
host.opened(selected
|
|
35
|
+
host.opened(selected)
|
|
36
36
|
// A correlated requester is normally `previous`; answer while its channel
|
|
37
37
|
// still exists, then retire the old mount.
|
|
38
38
|
host.reply(message, next.windowId)
|
|
@@ -57,41 +57,11 @@ export function selectResolvedView(
|
|
|
57
57
|
views: readonly ResolvedView[],
|
|
58
58
|
viewId: string | undefined,
|
|
59
59
|
): ResolvedView {
|
|
60
|
-
const selected = viewId
|
|
60
|
+
const selected = viewId
|
|
61
|
+
? views.find((view) => view.route.key === viewId || `/:${view.route.key}` === viewId)
|
|
62
|
+
: views[0]
|
|
61
63
|
if (selected) return selected
|
|
62
64
|
throw new Error(
|
|
63
65
|
viewId ? `View ${viewId} does not resolve for this node` : 'No view resolves for this node',
|
|
64
66
|
)
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
/** Mount a shell view with bounded handshake retries and a plain fallback. */
|
|
68
|
-
export async function mountWithHandshakeFallback<T>(opts: {
|
|
69
|
-
handshake: 'shell' | 'none'
|
|
70
|
-
attempts: number
|
|
71
|
-
mount(handshake: 'shell' | 'none'): Promise<T>
|
|
72
|
-
cleanupFailedAttempt(): void
|
|
73
|
-
}): Promise<{ mounted: T; handshake: 'shell' | 'none' }> {
|
|
74
|
-
if (opts.handshake === 'none') {
|
|
75
|
-
try {
|
|
76
|
-
return { mounted: await opts.mount('none'), handshake: 'none' }
|
|
77
|
-
} catch (error) {
|
|
78
|
-
opts.cleanupFailedAttempt()
|
|
79
|
-
throw error
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
let lastError: unknown
|
|
83
|
-
for (let attempt = 0; attempt < opts.attempts; attempt++) {
|
|
84
|
-
try {
|
|
85
|
-
return { mounted: await opts.mount('shell'), handshake: 'shell' }
|
|
86
|
-
} catch (error) {
|
|
87
|
-
lastError = error
|
|
88
|
-
opts.cleanupFailedAttempt()
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
return { mounted: await opts.mount('none'), handshake: 'none' }
|
|
93
|
-
} catch (error) {
|
|
94
|
-
opts.cleanupFailedAttempt()
|
|
95
|
-
throw error ?? lastError
|
|
96
|
-
}
|
|
97
|
-
}
|