@astrale-os/cli 0.4.0-alpha.13
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/.check-workspace.cjs +40 -0
- package/README.md +151 -0
- package/dist/astrale.js +59749 -0
- package/package.json +90 -0
- package/src/command.ts +40 -0
- package/src/commands/__tests__/admin-instance.test.ts +73 -0
- package/src/commands/__tests__/auth-login.test.ts +178 -0
- package/src/commands/__tests__/auth-token.test.ts +223 -0
- package/src/commands/__tests__/call.test.ts +72 -0
- package/src/commands/__tests__/domain-list.test.ts +74 -0
- package/src/commands/__tests__/help-contract.test.ts +136 -0
- package/src/commands/__tests__/install-identity-override.test.ts +65 -0
- package/src/commands/__tests__/instance-bookmark.test.ts +101 -0
- package/src/commands/__tests__/instance-create-hosts.test.ts +29 -0
- package/src/commands/__tests__/instance-list-rows.test.ts +63 -0
- package/src/commands/__tests__/logs.test.ts +117 -0
- package/src/commands/__tests__/ls.test.ts +25 -0
- package/src/commands/__tests__/setup-plan.test.ts +61 -0
- package/src/commands/admin/status.ts +61 -0
- package/src/commands/admin/use.ts +77 -0
- package/src/commands/auth/login.ts +82 -0
- package/src/commands/auth/logout.ts +50 -0
- package/src/commands/auth/status.ts +86 -0
- package/src/commands/auth/token.ts +162 -0
- package/src/commands/browser.ts +207 -0
- package/src/commands/call.ts +300 -0
- package/src/commands/describe.ts +182 -0
- package/src/commands/domain/install.ts +420 -0
- package/src/commands/domain/list.ts +154 -0
- package/src/commands/domain/publish.ts +155 -0
- package/src/commands/get.ts +60 -0
- package/src/commands/identity/create.ts +26 -0
- package/src/commands/identity/delete.ts +18 -0
- package/src/commands/identity/export.ts +66 -0
- package/src/commands/identity/import.ts +101 -0
- package/src/commands/identity/list.ts +56 -0
- package/src/commands/identity/register.ts +170 -0
- package/src/commands/identity/sync.ts +32 -0
- package/src/commands/identity/unsync.ts +24 -0
- package/src/commands/identity/use.ts +18 -0
- package/src/commands/identity/whoami.ts +34 -0
- package/src/commands/idp/add.ts +150 -0
- package/src/commands/idp/list.ts +57 -0
- package/src/commands/idp/refresh.ts +38 -0
- package/src/commands/idp/remove.ts +36 -0
- package/src/commands/idp/show.ts +29 -0
- package/src/commands/instance/active.ts +64 -0
- package/src/commands/instance/bookmark.ts +72 -0
- package/src/commands/instance/create.ts +69 -0
- package/src/commands/instance/delete.ts +72 -0
- package/src/commands/instance/forget.ts +26 -0
- package/src/commands/instance/list.ts +149 -0
- package/src/commands/instance/status.ts +42 -0
- package/src/commands/instance/use.ts +210 -0
- package/src/commands/logs.ts +347 -0
- package/src/commands/ls.ts +229 -0
- package/src/commands/query.ts +32 -0
- package/src/commands/setup.ts +54 -0
- package/src/commands/status.ts +60 -0
- package/src/commands/studio.ts +401 -0
- package/src/commands/token.ts +77 -0
- package/src/commands/update.ts +267 -0
- package/src/commands/use.ts +87 -0
- package/src/errors.ts +65 -0
- package/src/kernel/__tests__/auth.test.ts +77 -0
- package/src/kernel/__tests__/errors.test.ts +43 -0
- package/src/kernel/__tests__/remote-routing.test.ts +70 -0
- package/src/kernel/auth.ts +234 -0
- package/src/kernel/ca-fetch.ts +119 -0
- package/src/kernel/client.ts +191 -0
- package/src/kernel/errors.ts +280 -0
- package/src/kernel/expand.ts +217 -0
- package/src/kernel/index.ts +14 -0
- package/src/kernel/options.ts +22 -0
- package/src/kernel/remote-routing.ts +88 -0
- package/src/kernel/run.ts +63 -0
- package/src/kernel/types.ts +14 -0
- package/src/lib/__tests__/admin-target.test.ts +112 -0
- package/src/lib/__tests__/binary.test.ts +56 -0
- package/src/lib/__tests__/command-dx.test.ts +58 -0
- package/src/lib/__tests__/concurrency.test.ts +62 -0
- package/src/lib/__tests__/config.test.ts +53 -0
- package/src/lib/__tests__/design.test.ts +99 -0
- package/src/lib/__tests__/domain-identity.test.ts +60 -0
- package/src/lib/__tests__/format.test.ts +22 -0
- package/src/lib/__tests__/fs-atomic.test.ts +104 -0
- package/src/lib/__tests__/identity.test.ts +79 -0
- package/src/lib/__tests__/idp-session.driver.ts +53 -0
- package/src/lib/__tests__/idp-session.test.ts +357 -0
- package/src/lib/__tests__/idp.test.ts +385 -0
- package/src/lib/__tests__/instance-candidates.test.ts +73 -0
- package/src/lib/__tests__/instance-target.test.ts +183 -0
- package/src/lib/__tests__/instance.test.ts +136 -0
- package/src/lib/__tests__/keys.test.ts +129 -0
- package/src/lib/__tests__/local-status.test.ts +202 -0
- package/src/lib/__tests__/output.test.ts +150 -0
- package/src/lib/__tests__/panel.test.ts +40 -0
- package/src/lib/__tests__/port.test.ts +44 -0
- package/src/lib/__tests__/prompt.test.ts +25 -0
- package/src/lib/__tests__/sdk-deps.test.ts +68 -0
- package/src/lib/__tests__/self.test.ts +272 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +74 -0
- package/src/lib/__tests__/table.test.ts +53 -0
- package/src/lib/__tests__/update.test.ts +246 -0
- package/src/lib/__tests__/use-target.test.ts +56 -0
- package/src/lib/__tests__/validation.test.ts +34 -0
- package/src/lib/admin-domain.ts +25 -0
- package/src/lib/admin-instance.ts +26 -0
- package/src/lib/admin-target.ts +217 -0
- package/src/lib/binary.ts +131 -0
- package/src/lib/browser.ts +150 -0
- package/src/lib/command-dx.ts +161 -0
- package/src/lib/concurrency.ts +31 -0
- package/src/lib/config.ts +45 -0
- package/src/lib/domain-identity.ts +49 -0
- package/src/lib/env.ts +49 -0
- package/src/lib/format.ts +4 -0
- package/src/lib/fs-atomic.ts +126 -0
- package/src/lib/identity.ts +256 -0
- package/src/lib/idp-session.ts +134 -0
- package/src/lib/idp.ts +876 -0
- package/src/lib/instance-candidates.ts +49 -0
- package/src/lib/instance-target.ts +182 -0
- package/src/lib/instance.ts +395 -0
- package/src/lib/keys.ts +294 -0
- package/src/lib/local-status.ts +152 -0
- package/src/lib/log.ts +116 -0
- package/src/lib/login-flow.ts +164 -0
- package/src/lib/meta.ts +86 -0
- package/src/lib/output.ts +222 -0
- package/src/lib/panel.ts +61 -0
- package/src/lib/paths.ts +11 -0
- package/src/lib/port.ts +41 -0
- package/src/lib/proc.ts +82 -0
- package/src/lib/prompt.ts +136 -0
- package/src/lib/provision-instance.ts +170 -0
- package/src/lib/sdk-deps.ts +104 -0
- package/src/lib/self.ts +166 -0
- package/src/lib/skills.ts +171 -0
- package/src/lib/table.ts +62 -0
- package/src/lib/update.ts +315 -0
- package/src/lib/use-target.ts +24 -0
- package/src/lib/validation.ts +59 -0
- package/src/program.ts +200 -0
- package/src/registry.ts +59 -0
- package/src/setup/__tests__/util.test.ts +29 -0
- package/src/setup/engine.ts +83 -0
- package/src/setup/render.ts +109 -0
- package/src/setup/steps/admin.ts +78 -0
- package/src/setup/steps/agent-browser.ts +81 -0
- package/src/setup/steps/auth.ts +54 -0
- package/src/setup/steps/domain.ts +68 -0
- package/src/setup/steps/index.ts +18 -0
- package/src/setup/steps/instance.ts +119 -0
- package/src/setup/steps/skills-bridge.ts +70 -0
- package/src/setup/steps/skills.ts +59 -0
- package/src/setup/types.ts +61 -0
- package/src/setup/util.ts +34 -0
- package/src/test-utils.ts +18 -0
- package/studio/client/dist/assets/index-DOwzZAEK.css +1 -0
- package/studio/client/dist/assets/index-wtU0Zxhy.js +183 -0
- package/studio/client/dist/index.html +13 -0
- package/studio/package.json +62 -0
- package/studio/server/agent/ask.ts +68 -0
- package/studio/server/agent/bridge-mcp.ts +182 -0
- package/studio/server/agent/bridge.ts +188 -0
- package/studio/server/agent/claude.ts +666 -0
- package/studio/server/agent/mock.ts +186 -0
- package/studio/server/agent/prompt.ts +202 -0
- package/studio/server/agent/registry.ts +29 -0
- package/studio/server/agent/runner.ts +484 -0
- package/studio/server/agent/schema-map.ts +112 -0
- package/studio/server/agent/types.ts +120 -0
- package/studio/server/api.ts +574 -0
- package/studio/server/cache.ts +138 -0
- package/studio/server/detect.ts +81 -0
- package/studio/server/domain.ts +70 -0
- package/studio/server/index.ts +136 -0
- package/studio/server/introspect/anatomy-extras.ts +398 -0
- package/studio/server/introspect/anatomy.ts +108 -0
- package/studio/server/introspect/bundle.ts +57 -0
- package/studio/server/introspect/core-extractor.ts +119 -0
- package/studio/server/introspect/core.ts +44 -0
- package/studio/server/introspect/diff.ts +133 -0
- package/studio/server/introspect/extractor.ts +102 -0
- package/studio/server/introspect/hash.ts +21 -0
- package/studio/server/introspect/overlay-tsmorph.ts +874 -0
- package/studio/server/introspect/overlay.ts +57 -0
- package/studio/server/introspect/runtime.ts +99 -0
- package/studio/server/introspect/schema-refs.ts +46 -0
- package/studio/server/lifecycle.ts +38 -0
- package/studio/server/sse.ts +57 -0
- package/studio/server/state/baseline.ts +211 -0
- package/studio/server/state/catalog.ts +117 -0
- package/studio/server/state/comments.ts +321 -0
- package/studio/server/state/context.ts +167 -0
- package/studio/server/state/copy.ts +156 -0
- package/studio/server/state/create.ts +156 -0
- package/studio/server/state/documents.ts +70 -0
- package/studio/server/state/env.ts +161 -0
- package/studio/server/state/git.ts +75 -0
- package/studio/server/state/handoff.ts +55 -0
- package/studio/server/state/harness-gateway.ts +181 -0
- package/studio/server/state/harness-token.ts +0 -0
- package/studio/server/state/instance.ts +244 -0
- package/studio/server/state/integrations.ts +55 -0
- package/studio/server/state/layout.ts +55 -0
- package/studio/server/state/settings.ts +39 -0
- package/studio/server/state/store.ts +97 -0
- package/studio/server/state/updates.ts +63 -0
- package/studio/server/state/usage.ts +37 -0
- package/studio/server/state/views.ts +138 -0
- package/studio/server/state/visibility.ts +33 -0
- package/studio/server/watch.ts +81 -0
- package/studio/server/workspace-state.ts +26 -0
- package/studio/server/workspace-watch.ts +101 -0
- package/studio/shared/types.ts +873 -0
- package/studio/tsconfig.json +23 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
import type { CommandDefinition } from '../../command'
|
|
4
|
+
|
|
5
|
+
import { resolveAdminTargetFromStore } from '../../lib/admin-target'
|
|
6
|
+
import { readConfig } from '../../lib/config'
|
|
7
|
+
import { getDefault } from '../../lib/identity'
|
|
8
|
+
import { readInstances } from '../../lib/instance'
|
|
9
|
+
import { fatal, log } from '../../lib/log'
|
|
10
|
+
import { isMachine, output, RAW_OUTPUT_OPTIONS, type RawOutputOpts } from '../../lib/output'
|
|
11
|
+
|
|
12
|
+
type AdminStatus = {
|
|
13
|
+
target: ReturnType<typeof resolveAdminTargetFromStore>
|
|
14
|
+
identity: {
|
|
15
|
+
name: string
|
|
16
|
+
registered: boolean
|
|
17
|
+
} | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
name: 'status',
|
|
22
|
+
description: 'Show configured admin kernel target',
|
|
23
|
+
options: [...RAW_OUTPUT_OPTIONS],
|
|
24
|
+
action: async (opts: RawOutputOpts) => {
|
|
25
|
+
try {
|
|
26
|
+
const [config, instances, identity] = await Promise.all([
|
|
27
|
+
readConfig(),
|
|
28
|
+
readInstances(),
|
|
29
|
+
getDefault().catch(() => null),
|
|
30
|
+
])
|
|
31
|
+
const target = resolveAdminTargetFromStore({}, config, instances)
|
|
32
|
+
const status: AdminStatus = {
|
|
33
|
+
target,
|
|
34
|
+
identity: identity
|
|
35
|
+
? {
|
|
36
|
+
name: identity.name,
|
|
37
|
+
registered: !!identity.registrations?.[target.registrationSlug],
|
|
38
|
+
}
|
|
39
|
+
: null,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isMachine(opts)) {
|
|
43
|
+
output(status, opts)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(chalk.bold('Admin'))
|
|
48
|
+
console.log(` ${chalk.bold(target.name)} ${chalk.dim(target.url)}`)
|
|
49
|
+
console.log(` issuer: ${chalk.dim(target.issuer)}`)
|
|
50
|
+
console.log(` source: ${target.source}`)
|
|
51
|
+
if (status.identity) {
|
|
52
|
+
const mark = status.identity.registered ? chalk.green('yes') : chalk.yellow('no')
|
|
53
|
+
console.log(` identity ${status.identity.name} registered: ${mark}`)
|
|
54
|
+
} else {
|
|
55
|
+
log.dim(' No default identity. Run: astrale identity create <name>')
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
fatal(e)
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../command'
|
|
2
|
+
|
|
3
|
+
import { AdminTargetConfigSchema, DEFAULT_ADMIN_TARGET_NAME } from '../../lib/admin-target'
|
|
4
|
+
import { readConfig, writeConfig } from '../../lib/config'
|
|
5
|
+
import { resolveInstance } from '../../lib/instance'
|
|
6
|
+
import { fatal, log } from '../../lib/log'
|
|
7
|
+
|
|
8
|
+
type AdminUseOpts = {
|
|
9
|
+
url?: string
|
|
10
|
+
name?: string
|
|
11
|
+
issuer?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
name: 'use',
|
|
16
|
+
description: 'Set the admin kernel target',
|
|
17
|
+
afterHelpText: `
|
|
18
|
+
Behavior:
|
|
19
|
+
The admin target is the admin kernel used by admin-backed commands such as
|
|
20
|
+
\`astrale instance create\`, \`astrale instance list\`, and
|
|
21
|
+
\`astrale instance delete\`. It is independent from the active instance used
|
|
22
|
+
by normal kernel calls.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
$ astrale admin use admin
|
|
26
|
+
$ astrale admin use --url https://admin.eu.astrale.ai/api
|
|
27
|
+
$ astrale admin status
|
|
28
|
+
`,
|
|
29
|
+
arguments: [{ name: 'bookmark', description: 'Bookmarked admin instance', required: false }],
|
|
30
|
+
options: [
|
|
31
|
+
{ flags: '--url <url>', description: 'Direct admin kernel URL' },
|
|
32
|
+
{
|
|
33
|
+
flags: '--name <name>',
|
|
34
|
+
description: 'Friendly name / registration slug for direct URL admin target',
|
|
35
|
+
},
|
|
36
|
+
{ flags: '--issuer <url>', description: 'Admin kernel issuer/audience if different from URL' },
|
|
37
|
+
],
|
|
38
|
+
action: async (bookmark: string | undefined, opts: AdminUseOpts) => {
|
|
39
|
+
try {
|
|
40
|
+
if (bookmark && opts.url) {
|
|
41
|
+
fatal('Choose either a bookmark argument or --url, not both')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const config = await readConfig()
|
|
45
|
+
if (opts.url) {
|
|
46
|
+
const name = opts.name ?? DEFAULT_ADMIN_TARGET_NAME
|
|
47
|
+
await writeConfig({
|
|
48
|
+
...config,
|
|
49
|
+
admin: AdminTargetConfigSchema.parse({
|
|
50
|
+
name,
|
|
51
|
+
url: opts.url,
|
|
52
|
+
issuer: opts.issuer ?? opts.url,
|
|
53
|
+
}),
|
|
54
|
+
})
|
|
55
|
+
log.success(`Admin target: ${name} (${opts.url})`)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!bookmark) {
|
|
60
|
+
fatal(
|
|
61
|
+
'Missing admin target. Use: astrale admin use <bookmark> or astrale admin use --url <url>',
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolved = await resolveInstance(bookmark)
|
|
66
|
+
await writeConfig({
|
|
67
|
+
...config,
|
|
68
|
+
admin: AdminTargetConfigSchema.parse({
|
|
69
|
+
instance: resolved.name,
|
|
70
|
+
}),
|
|
71
|
+
})
|
|
72
|
+
log.success(`Admin target: ${resolved.name} (${resolved.url})`)
|
|
73
|
+
} catch (e) {
|
|
74
|
+
fatal(e)
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../command'
|
|
2
|
+
|
|
3
|
+
import { type IdpSession } from '../../lib/idp'
|
|
4
|
+
import { log } from '../../lib/log'
|
|
5
|
+
import { loginViaIdp, type LoginFlowOpts } from '../../lib/login-flow'
|
|
6
|
+
import { isMachine, output, RAW_OUTPUT_OPTIONS } from '../../lib/output'
|
|
7
|
+
|
|
8
|
+
type LoginOpts = LoginFlowOpts & {
|
|
9
|
+
device?: boolean
|
|
10
|
+
raw?: boolean
|
|
11
|
+
json?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
name: 'login',
|
|
16
|
+
description: 'Authenticate with an IdP and store an IdP-backed identity',
|
|
17
|
+
options: [
|
|
18
|
+
{ flags: '--idp <name>', description: 'IdP registry name (defaults when exactly one exists)' },
|
|
19
|
+
{ flags: '--name <name>', description: 'Local identity name to create/update' },
|
|
20
|
+
{ flags: '--scope <scope>', description: 'OAuth scope override' },
|
|
21
|
+
{ flags: '--audience <audience>', description: 'OAuth audience/resource parameter' },
|
|
22
|
+
{ flags: '--client-id <id>', description: 'OAuth client ID override' },
|
|
23
|
+
{ flags: '--client-secret-env <name>', description: 'Env var containing the client secret' },
|
|
24
|
+
{ flags: '--client-credentials', description: 'Use OAuth client_credentials grant' },
|
|
25
|
+
{ flags: '--device', description: 'Use OAuth device authorization flow (default)' },
|
|
26
|
+
{
|
|
27
|
+
flags: '--code <code>',
|
|
28
|
+
description: 'Exchange an authorization code instead of device auth',
|
|
29
|
+
},
|
|
30
|
+
{ flags: '--redirect-uri <url>', description: 'Redirect URI for authorization-code exchange' },
|
|
31
|
+
{
|
|
32
|
+
flags: '--code-verifier <value>',
|
|
33
|
+
description: 'PKCE verifier for authorization-code exchange',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
flags: '--no-use',
|
|
37
|
+
description: 'Do not switch the default identity to the logged-in identity',
|
|
38
|
+
},
|
|
39
|
+
...RAW_OUTPUT_OPTIONS,
|
|
40
|
+
],
|
|
41
|
+
afterHelpText: `
|
|
42
|
+
Examples:
|
|
43
|
+
$ astrale auth login --idp workos --device
|
|
44
|
+
$ astrale auth login --idp workos --client-credentials \\
|
|
45
|
+
--client-secret-env WORKOS_CLIENT_SECRET --audience https://api.example.com
|
|
46
|
+
$ astrale auth login --idp workos --code <code> --redirect-uri http://127.0.0.1:8787/callback
|
|
47
|
+
|
|
48
|
+
Notes:
|
|
49
|
+
Device auth is the default because it matches CLI use. Token values are
|
|
50
|
+
cached locally for credential resolution but are never printed.
|
|
51
|
+
`,
|
|
52
|
+
action: async (opts: LoginOpts) => {
|
|
53
|
+
const { session, identityName, idpName } = await loginViaIdp(opts)
|
|
54
|
+
|
|
55
|
+
if (isMachine(opts)) {
|
|
56
|
+
output(publicSession(session), opts)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
log.success(`Logged in as "${identityName}" via IdP "${idpName}"`)
|
|
61
|
+
log.dim(` subject: ${session.subject}`)
|
|
62
|
+
if (session.expires_at) log.dim(` expires_at: ${session.expires_at}`)
|
|
63
|
+
if (opts.use !== false) log.dim(' default identity updated')
|
|
64
|
+
},
|
|
65
|
+
} satisfies CommandDefinition
|
|
66
|
+
|
|
67
|
+
function publicSession(session: IdpSession): Record<string, unknown> {
|
|
68
|
+
return {
|
|
69
|
+
identity: session.identity,
|
|
70
|
+
idp: session.idp,
|
|
71
|
+
issuer: session.issuer,
|
|
72
|
+
subject: session.subject,
|
|
73
|
+
audience: session.audience,
|
|
74
|
+
token_type: session.token_type,
|
|
75
|
+
scope: session.scope,
|
|
76
|
+
expires_at: session.expires_at,
|
|
77
|
+
has_access_token: !!session.access_token,
|
|
78
|
+
has_id_token: !!session.id_token,
|
|
79
|
+
has_refresh_token: !!session.refresh_token,
|
|
80
|
+
updatedAt: session.updatedAt,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../command'
|
|
2
|
+
|
|
3
|
+
import { getDefault, readIdentities } from '../../lib/identity'
|
|
4
|
+
import { deleteIdpSession, listIdpSessions } from '../../lib/idp'
|
|
5
|
+
import { log } from '../../lib/log'
|
|
6
|
+
import { output, RAW_OUTPUT_OPTIONS } from '../../lib/output'
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: 'logout',
|
|
10
|
+
description: 'Clear cached IdP session tokens',
|
|
11
|
+
options: [
|
|
12
|
+
{ flags: '--name <name>', description: 'Identity session to clear' },
|
|
13
|
+
{ flags: '--all', description: 'Clear every cached IdP session' },
|
|
14
|
+
...RAW_OUTPUT_OPTIONS,
|
|
15
|
+
],
|
|
16
|
+
action: async (opts: { name?: string; all?: boolean; raw?: boolean; json?: boolean }) => {
|
|
17
|
+
let names: string[]
|
|
18
|
+
if (opts.all) {
|
|
19
|
+
names = (await listIdpSessions()).map((session) => session.identity)
|
|
20
|
+
} else if (opts.name) {
|
|
21
|
+
names = [opts.name]
|
|
22
|
+
} else {
|
|
23
|
+
const identity = await getDefault()
|
|
24
|
+
if ((identity.source ?? 'key') !== 'idp') {
|
|
25
|
+
throw new Error('Default identity is not IdP-backed. Pass --name or --all.')
|
|
26
|
+
}
|
|
27
|
+
names = [identity.name]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const name of names) await deleteIdpSession(name)
|
|
31
|
+
|
|
32
|
+
if (opts.raw || opts.json) {
|
|
33
|
+
output({ cleared: names }, opts)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
if (names.length === 0) {
|
|
37
|
+
log.dim(' No cached IdP sessions')
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
log.success(`Cleared IdP session${names.length === 1 ? '' : 's'}: ${names.join(', ')}`)
|
|
41
|
+
|
|
42
|
+
const store = await readIdentities()
|
|
43
|
+
const stale = names.filter((name) => store.identities[name]?.source === 'idp')
|
|
44
|
+
if (stale.length > 0) {
|
|
45
|
+
log.dim(
|
|
46
|
+
' IdP identity records remain; delete them with `astrale identity delete <name>` if needed.',
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
import type { CommandDefinition } from '../../command'
|
|
4
|
+
import type { ListOpts, ListProjection } from '../../lib/output'
|
|
5
|
+
|
|
6
|
+
import { readIdentities } from '../../lib/identity'
|
|
7
|
+
import { isSessionExpired, listIdpSessions } from '../../lib/idp'
|
|
8
|
+
import { log } from '../../lib/log'
|
|
9
|
+
import { isMachine, presentList, RAW_OUTPUT_OPTIONS } from '../../lib/output'
|
|
10
|
+
|
|
11
|
+
type AuthRow = {
|
|
12
|
+
name: string
|
|
13
|
+
default: boolean
|
|
14
|
+
idp?: string
|
|
15
|
+
issuer?: string
|
|
16
|
+
subject: string
|
|
17
|
+
session:
|
|
18
|
+
| {
|
|
19
|
+
cached: true
|
|
20
|
+
expired: boolean
|
|
21
|
+
expires_at?: string
|
|
22
|
+
scope?: string
|
|
23
|
+
has_refresh_token: boolean
|
|
24
|
+
}
|
|
25
|
+
| { cached: false }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sessionState(s: AuthRow['session']): string {
|
|
29
|
+
if (!s.cached) return chalk.dim('no session')
|
|
30
|
+
return s.expired ? chalk.yellow('expired') : chalk.green('active')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function projection(items: AuthRow[]): ListProjection {
|
|
34
|
+
return {
|
|
35
|
+
columns: [
|
|
36
|
+
{ key: 'name', header: 'NAME', color: chalk.bold },
|
|
37
|
+
{ key: 'session', header: 'SESSION' },
|
|
38
|
+
{ key: 'idp', header: 'IDP', color: chalk.dim },
|
|
39
|
+
{ key: 'subject', header: 'SUBJECT', color: chalk.dim },
|
|
40
|
+
],
|
|
41
|
+
rows: items.map((i) => ({
|
|
42
|
+
name: i.default ? `${i.name} ${chalk.green('*')}` : i.name,
|
|
43
|
+
session: sessionState(i.session),
|
|
44
|
+
idp: i.idp ?? '',
|
|
45
|
+
subject: i.subject,
|
|
46
|
+
})),
|
|
47
|
+
paths: items.map((i) => i.name),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
name: 'status',
|
|
53
|
+
description: 'Show cached IdP authentication status',
|
|
54
|
+
options: [...RAW_OUTPUT_OPTIONS],
|
|
55
|
+
action: async (opts: ListOpts) => {
|
|
56
|
+
const [identityStore, sessions] = await Promise.all([readIdentities(), listIdpSessions()])
|
|
57
|
+
const byIdentity = new Map(sessions.map((session) => [session.identity, session]))
|
|
58
|
+
const items: AuthRow[] = Object.entries(identityStore.identities)
|
|
59
|
+
.filter(([, identity]) => identity.source === 'idp')
|
|
60
|
+
.map(([name, identity]) => {
|
|
61
|
+
const session = byIdentity.get(name)
|
|
62
|
+
return {
|
|
63
|
+
name,
|
|
64
|
+
default: identityStore.default === name,
|
|
65
|
+
idp: identity.idp,
|
|
66
|
+
issuer: identity.issuer,
|
|
67
|
+
subject: identity.subject,
|
|
68
|
+
session: session
|
|
69
|
+
? {
|
|
70
|
+
cached: true,
|
|
71
|
+
expired: isSessionExpired(session),
|
|
72
|
+
expires_at: session.expires_at,
|
|
73
|
+
scope: session.scope,
|
|
74
|
+
has_refresh_token: !!session.refresh_token,
|
|
75
|
+
}
|
|
76
|
+
: { cached: false },
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
if (items.length === 0 && !isMachine(opts)) {
|
|
81
|
+
log.dim(' Not logged in to any IdP. Run: astrale auth login --idp <name>')
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
presentList(items, opts, projection)
|
|
85
|
+
},
|
|
86
|
+
} satisfies CommandDefinition
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { CommandDefinition } from '../../command'
|
|
2
|
+
|
|
3
|
+
import { getDefault, getIdentity, readIdentities } from '../../lib/identity'
|
|
4
|
+
import { isSessionExpired, readIdpSession, type IdpSession } from '../../lib/idp'
|
|
5
|
+
import { ensureFreshSession } from '../../lib/idp-session'
|
|
6
|
+
import { log } from '../../lib/log'
|
|
7
|
+
import { output } from '../../lib/output'
|
|
8
|
+
|
|
9
|
+
type AuthTokenType = 'access' | 'id'
|
|
10
|
+
|
|
11
|
+
type AuthTokenOpts = {
|
|
12
|
+
name?: string
|
|
13
|
+
idp?: string
|
|
14
|
+
type?: AuthTokenType
|
|
15
|
+
refresh?: boolean
|
|
16
|
+
raw?: boolean
|
|
17
|
+
json?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type AuthTokenResult = {
|
|
21
|
+
identity: string
|
|
22
|
+
idp: string
|
|
23
|
+
issuer: string
|
|
24
|
+
subject: string
|
|
25
|
+
type: AuthTokenType
|
|
26
|
+
token: string
|
|
27
|
+
token_type?: string
|
|
28
|
+
scope?: string
|
|
29
|
+
expires_at?: string
|
|
30
|
+
expired: boolean
|
|
31
|
+
refreshed: boolean
|
|
32
|
+
updatedAt: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default {
|
|
36
|
+
name: 'token',
|
|
37
|
+
description: 'Print a cached IdP provider token for an IdP-backed identity',
|
|
38
|
+
options: [
|
|
39
|
+
{
|
|
40
|
+
flags: '--name <name>',
|
|
41
|
+
description: 'IdP-backed identity name (defaults to active identity)',
|
|
42
|
+
},
|
|
43
|
+
{ flags: '--idp <name>', description: 'Select the IdP when identity name is omitted' },
|
|
44
|
+
{
|
|
45
|
+
flags: '--type <type>',
|
|
46
|
+
description: 'Token type to print',
|
|
47
|
+
choices: ['access', 'id'],
|
|
48
|
+
default: 'access',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
flags: '--no-refresh',
|
|
52
|
+
description: 'Do not refresh an expired cached session before printing',
|
|
53
|
+
},
|
|
54
|
+
{ flags: '--raw', description: 'Print only the token string' },
|
|
55
|
+
{ flags: '--json', description: 'Print JSON metadata including the token' },
|
|
56
|
+
],
|
|
57
|
+
afterHelpText: `
|
|
58
|
+
Behavior:
|
|
59
|
+
Defaults to the active IdP-backed identity. If the cached session is expired,
|
|
60
|
+
the CLI refreshes it before printing unless --no-refresh is passed. --raw
|
|
61
|
+
prints only the token string for shell use.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
$ astrale auth token --raw
|
|
65
|
+
$ astrale auth token --idp workos --raw
|
|
66
|
+
$ astrale auth token --name alice --type id --json
|
|
67
|
+
`,
|
|
68
|
+
action: async (opts: AuthTokenOpts) => {
|
|
69
|
+
const result = await resolveAuthToken(opts)
|
|
70
|
+
|
|
71
|
+
if (opts.raw) {
|
|
72
|
+
process.stdout.write(result.token + '\n')
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (opts.json) {
|
|
77
|
+
output(result, { json: true })
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
log.dim(` (${result.idp} ${result.type}_token for identity "${result.identity}" - secret)`)
|
|
82
|
+
process.stdout.write(result.token + '\n')
|
|
83
|
+
},
|
|
84
|
+
} satisfies CommandDefinition
|
|
85
|
+
|
|
86
|
+
export async function resolveAuthToken(opts: AuthTokenOpts): Promise<AuthTokenResult> {
|
|
87
|
+
const identityName = await resolveIdentityName(opts)
|
|
88
|
+
const session = await readIdpSession(identityName)
|
|
89
|
+
if (!session) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`No cached IdP session for "${identityName}". Run: astrale auth login --name ${identityName}`,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ensureFreshSession serializes the (single-use) refresh-token exchange
|
|
96
|
+
// across concurrent CLI processes and is a no-op while the token is fresh.
|
|
97
|
+
const resolved = opts.refresh !== false ? await ensureFreshSession(identityName) : session
|
|
98
|
+
const type = opts.type ?? 'access'
|
|
99
|
+
const token = tokenFromSession(resolved, type)
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
identity: resolved.identity,
|
|
103
|
+
idp: resolved.idp,
|
|
104
|
+
issuer: resolved.issuer,
|
|
105
|
+
subject: resolved.subject,
|
|
106
|
+
type,
|
|
107
|
+
token,
|
|
108
|
+
token_type: resolved.token_type,
|
|
109
|
+
scope: resolved.scope,
|
|
110
|
+
expires_at: resolved.expires_at,
|
|
111
|
+
expired: isSessionExpired(resolved),
|
|
112
|
+
refreshed: resolved.updatedAt !== session.updatedAt,
|
|
113
|
+
updatedAt: resolved.updatedAt,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function resolveIdentityName(opts: AuthTokenOpts): Promise<string> {
|
|
118
|
+
if (opts.name) {
|
|
119
|
+
const identity = await getIdentity(opts.name)
|
|
120
|
+
if ((identity.source ?? 'key') !== 'idp') {
|
|
121
|
+
throw new Error(`Identity "${opts.name}" is not IdP-backed`)
|
|
122
|
+
}
|
|
123
|
+
if (opts.idp && identity.idp !== opts.idp) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Identity "${opts.name}" is backed by IdP "${identity.idp ?? '?'}", not "${opts.idp}"`,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
return opts.name
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (opts.idp) {
|
|
132
|
+
const store = await readIdentities()
|
|
133
|
+
const matches = Object.entries(store.identities)
|
|
134
|
+
.filter(([, identity]) => identity.source === 'idp' && identity.idp === opts.idp)
|
|
135
|
+
.map(([name]) => name)
|
|
136
|
+
|
|
137
|
+
if (matches.length === 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`No IdP-backed identities found for IdP "${opts.idp}". Run: astrale auth login --idp ${opts.idp}`,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
if (matches.includes(store.default)) return store.default
|
|
143
|
+
if (matches.length === 1) return matches[0]
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Multiple IdP-backed identities found for IdP "${opts.idp}": ${matches.join(', ')}. Pass --name.`,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const identity = await getDefault()
|
|
150
|
+
if ((identity.source ?? 'key') !== 'idp') {
|
|
151
|
+
throw new Error('Default identity is not IdP-backed. Pass --name or --idp.')
|
|
152
|
+
}
|
|
153
|
+
return identity.name
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function tokenFromSession(session: IdpSession, type: AuthTokenType): string {
|
|
157
|
+
if (type === 'access') return session.access_token
|
|
158
|
+
if (session.id_token) return session.id_token
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Cached IdP session for "${session.identity}" has no id_token. Use --type access.`,
|
|
161
|
+
)
|
|
162
|
+
}
|