@ritualai/cli 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/dist/commands/doctor.js +87 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +158 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.js +63 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.js +25 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/refresh.js +42 -0
- package/dist/commands/refresh.js.map +1 -0
- package/dist/commands/whoami.js +52 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/agents/configure-mcp.js +127 -0
- package/dist/lib/agents/configure-mcp.js.map +1 -0
- package/dist/lib/agents/detector.js +32 -0
- package/dist/lib/agents/detector.js.map +1 -0
- package/dist/lib/agents/providers.js +118 -0
- package/dist/lib/agents/providers.js.map +1 -0
- package/dist/lib/api-client.js +120 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/config.js +154 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/oidc.js +213 -0
- package/dist/lib/oidc.js.map +1 -0
- package/dist/lib/pat-store.js +44 -0
- package/dist/lib/pat-store.js.map +1 -0
- package/dist/lib/skill-copy.js +57 -0
- package/dist/lib/skill-copy.js.map +1 -0
- package/package.json +59 -0
- package/skills/claude-code/ritual/SKILL.md +271 -0
- package/skills/codex/ritual/SKILL.md +271 -0
- package/skills/cursor/ritual/SKILL.md +271 -0
- package/skills/gemini/ritual/SKILL.md +271 -0
- package/skills/kiro/ritual/SKILL.md +271 -0
- package/skills/vscode/ritual/SKILL.md +271 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# `@ritualai/cli`
|
|
2
|
+
|
|
3
|
+
The Ritual CLI. Connects your AI coding agent (Claude Code, Cursor,
|
|
4
|
+
Codex, Kiro, Gemini CLI, Windsurf, etc.) to Ritual Cloud via the
|
|
5
|
+
[Model Context Protocol][mcp].
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @ritualai/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
ritual login # browser-based sign-in (OAuth 2.1 + PKCE)
|
|
17
|
+
ritual init # scaffold skills + register MCP with every detected agent
|
|
18
|
+
ritual doctor # sanity-check the environment
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That's it. `init` does three things:
|
|
22
|
+
|
|
23
|
+
1. Detects which AI coding agents you have installed (Claude Code,
|
|
24
|
+
Cursor, Windsurf, Kiro, Gemini CLI, VS Code / Copilot, Codex).
|
|
25
|
+
2. Mints a long-lived **Personal Access Token** via the Ritual API
|
|
26
|
+
named `Ritual CLI — <hostname>`, so the agent has a credential
|
|
27
|
+
that doesn't expire every 5 minutes the way OAuth tokens do.
|
|
28
|
+
3. For each detected agent: copies the Ritual skill files into your
|
|
29
|
+
project (`.claude/skills/`, `.cursor/rules/`, etc.) AND writes
|
|
30
|
+
the MCP server config (`claude mcp add-json` for Claude Code, or
|
|
31
|
+
`mcp.json` merge for the others) with the PAT as the Bearer.
|
|
32
|
+
|
|
33
|
+
Restart your agent after `init` so it picks up the new MCP server,
|
|
34
|
+
then try `/ritual build <feature>` from inside the agent.
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ritual init [--agent <id>] [--list] # scaffold + register
|
|
40
|
+
ritual login # browser sign-in
|
|
41
|
+
ritual logout # clear creds
|
|
42
|
+
ritual whoami # session info, auto-refresh
|
|
43
|
+
ritual refresh # force token refresh
|
|
44
|
+
ritual doctor # env + detection report
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The agent ids are: `claude-code`, `cursor`, `windsurf`, `kiro`,
|
|
48
|
+
`gemini`, `vscode`, `codex`. Use `--agent <id>` if you want to scaffold
|
|
49
|
+
for a specific one without auto-detecting (handy when you've got the
|
|
50
|
+
agent installed somewhere non-standard).
|
|
51
|
+
|
|
52
|
+
## How refresh works (v0.2)
|
|
53
|
+
|
|
54
|
+
Keycloak issues a short-lived access token (~5 min by default) and a
|
|
55
|
+
longer-lived refresh token (~30 min). The CLI handles this transparently:
|
|
56
|
+
|
|
57
|
+
- `ritual whoami` and any future Bearer-using command call
|
|
58
|
+
`getValidAccessToken()` internally. If the cached access token is
|
|
59
|
+
still fresh (>30s of life left), they use it as-is. If it's expired
|
|
60
|
+
but the refresh token is still good, they exchange it for a new
|
|
61
|
+
access token and persist the result before continuing.
|
|
62
|
+
- If the refresh token is also expired/revoked (Keycloak returns
|
|
63
|
+
`400 invalid_grant`), you'll see a clear "session expired, run
|
|
64
|
+
`ritual login`" message — not a confusing 401 from the API.
|
|
65
|
+
- `ritual refresh` exposes the refresh path explicitly for scripts
|
|
66
|
+
and debugging. Most users never need to run it directly.
|
|
67
|
+
|
|
68
|
+
Refresh-token rotation (Keycloak's default) means the stored refresh
|
|
69
|
+
token changes on every successful refresh — the CLI persists the new
|
|
70
|
+
one automatically.
|
|
71
|
+
|
|
72
|
+
## How sign-in works
|
|
73
|
+
|
|
74
|
+
1. CLI binds a one-shot HTTP server on a random localhost port.
|
|
75
|
+
2. CLI opens your browser to the Ritual identity provider (Keycloak).
|
|
76
|
+
3. You authenticate (or use SSO if your org has it configured).
|
|
77
|
+
4. Keycloak redirects back to `http://127.0.0.1:<port>/callback` with
|
|
78
|
+
an authorization code.
|
|
79
|
+
5. CLI exchanges the code at the token endpoint using PKCE proof.
|
|
80
|
+
6. Tokens are stored at `~/.config/ritual/credentials.json` (chmod 0600).
|
|
81
|
+
|
|
82
|
+
This is the standard [RFC 8252][rfc8252] loopback pattern — no shared
|
|
83
|
+
secret, no manual API key copy-paste.
|
|
84
|
+
|
|
85
|
+
## Override defaults
|
|
86
|
+
|
|
87
|
+
Defaults point at production. For dev, either:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ritual login --issuer https://auth.dev.ritualapp.cloud/realms/ritual
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
or set env vars:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
export RITUAL_KEYCLOAK_URL=https://auth.dev.ritualapp.cloud/realms/ritual
|
|
97
|
+
ritual login
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## What's next
|
|
101
|
+
|
|
102
|
+
- Per-agent skill *format transforms* (currently every agent gets the
|
|
103
|
+
same SKILL.md; future versions emit Cursor `.mdc`, Kiro YAML,
|
|
104
|
+
Gemini's specific frontmatter).
|
|
105
|
+
- Workflow commands (`ritual explore`, `ritual run`, `ritual brief`)
|
|
106
|
+
that wrap the MCP tools so you can drive Ritual from the terminal
|
|
107
|
+
outside an agent session.
|
|
108
|
+
|
|
109
|
+
## Security notes
|
|
110
|
+
|
|
111
|
+
- The credentials file is chmod 0600 (owner-only). On shared machines
|
|
112
|
+
this is your only protection — don't `chmod 644 ~/.config/ritual`.
|
|
113
|
+
- The CLI never sends your credentials anywhere except to Keycloak's
|
|
114
|
+
token endpoint over TLS.
|
|
115
|
+
- The `ritual-cli` Keycloak client is a public OAuth client (no
|
|
116
|
+
client secret). PKCE is what makes this safe — only the CLI process
|
|
117
|
+
that generated the verifier can exchange the code.
|
|
118
|
+
|
|
119
|
+
[mcp]: https://modelcontextprotocol.io
|
|
120
|
+
[rfc8252]: https://datatracker.ietf.org/doc/html/rfc8252
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.doctorCommand = doctorCommand;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_os_1 = require("node:os");
|
|
6
|
+
const config_1 = require("../lib/config");
|
|
7
|
+
const detector_1 = require("../lib/agents/detector");
|
|
8
|
+
const api_client_1 = require("../lib/api-client");
|
|
9
|
+
/**
|
|
10
|
+
* `ritual doctor` — sanity check the CLI's environment.
|
|
11
|
+
*
|
|
12
|
+
* Print, in order:
|
|
13
|
+
* 1. Runtime: node version, OS, hostname
|
|
14
|
+
* 2. Credentials: path + signed-in status + scopes + expiry
|
|
15
|
+
* 3. Resolved endpoints: API base, MCP URL (derived from issuer)
|
|
16
|
+
* 4. Agent detection: which agents we see installed
|
|
17
|
+
*
|
|
18
|
+
* Never modifies state. Always exits 0 unless the user asked for
|
|
19
|
+
* action items we can't complete (e.g. signed out — exit 1 so CI can
|
|
20
|
+
* gate on it).
|
|
21
|
+
*/
|
|
22
|
+
async function doctorCommand() {
|
|
23
|
+
let problems = 0;
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log(' Ritual CLI — environment report');
|
|
26
|
+
console.log('');
|
|
27
|
+
// --- Runtime --------------------------------------------------
|
|
28
|
+
console.log(' Runtime:');
|
|
29
|
+
console.log(` node ${process.version}`);
|
|
30
|
+
console.log(` platform ${(0, node_os_1.platform)()}`);
|
|
31
|
+
console.log(` hostname ${(0, node_os_1.hostname)()}`);
|
|
32
|
+
console.log('');
|
|
33
|
+
// --- Credentials ----------------------------------------------
|
|
34
|
+
console.log(' Credentials:');
|
|
35
|
+
console.log(` path ${(0, config_1.getCredentialsPath)()}`);
|
|
36
|
+
const tokenStatus = await (0, config_1.getValidAccessToken)();
|
|
37
|
+
if (tokenStatus.kind === 'not-signed-in') {
|
|
38
|
+
console.log(' state not signed in');
|
|
39
|
+
console.log(' fix run `ritual login`');
|
|
40
|
+
problems++;
|
|
41
|
+
console.log('');
|
|
42
|
+
}
|
|
43
|
+
else if (tokenStatus.kind === 're-auth-required') {
|
|
44
|
+
console.log(` state re-auth required (${tokenStatus.reason})`);
|
|
45
|
+
console.log(' fix run `ritual login`');
|
|
46
|
+
problems++;
|
|
47
|
+
console.log('');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const { creds } = tokenStatus;
|
|
51
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
52
|
+
const expiresInSec = creds.tokenSet.expires_at - nowSec;
|
|
53
|
+
console.log(` state signed in as ${creds.user?.email ?? creds.user?.sub ?? 'unknown'}`);
|
|
54
|
+
console.log(` issuer ${creds.issuer}`);
|
|
55
|
+
console.log(` expires in ${expiresInSec}s${tokenStatus.refreshed ? ' (auto-refreshed)' : ''}`);
|
|
56
|
+
console.log('');
|
|
57
|
+
// --- Resolved endpoints (only meaningful if signed in) ----
|
|
58
|
+
console.log(' Resolved endpoints:');
|
|
59
|
+
console.log(` api ${(0, api_client_1.apiBaseFromIssuer)(creds.issuer)}`);
|
|
60
|
+
console.log(` mcp ${(0, api_client_1.mcpUrlFromIssuer)(creds.issuer)}`);
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
// --- Agent detection ------------------------------------------
|
|
64
|
+
console.log(' AI coding agents:');
|
|
65
|
+
for (const a of (0, detector_1.detectAgents)()) {
|
|
66
|
+
const status = a.detected ? '✓' : '·';
|
|
67
|
+
// Pad id column so output aligns regardless of which agents
|
|
68
|
+
// the user has installed.
|
|
69
|
+
console.log(` ${status} ${a.id.padEnd(14)} ${a.name}`);
|
|
70
|
+
}
|
|
71
|
+
console.log('');
|
|
72
|
+
// --- Project state --------------------------------------------
|
|
73
|
+
// If we're inside a project that has any of the agent skill
|
|
74
|
+
// directories, mention it — useful for the "did init work" check.
|
|
75
|
+
const projectAgents = (0, detector_1.detectAgents)().filter((a) => (0, node_fs_1.existsSync)(`${process.cwd()}/${a.provider.projectSkillDir}`));
|
|
76
|
+
if (projectAgents.length > 0) {
|
|
77
|
+
console.log(' This project has skills for:');
|
|
78
|
+
for (const a of projectAgents) {
|
|
79
|
+
console.log(` ${a.provider.projectSkillDir}/`);
|
|
80
|
+
}
|
|
81
|
+
console.log('');
|
|
82
|
+
}
|
|
83
|
+
if (problems > 0) {
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":";;AAmBA,sCAqEC;AAxFD,qCAAqC;AACrC,qCAA6C;AAC7C,0CAAwE;AACxE,qDAAsD;AACtD,kDAAwE;AAExE;;;;;;;;;;;;GAYG;AACI,KAAK,UAAU,aAAa;IAClC,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,iEAAiE;IACjE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC1B,OAAO,CAAC,GAAG,CAAC,kBAAkB,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAA,kBAAQ,GAAE,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAA,kBAAQ,GAAE,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,iEAAiE;IACjE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAA,2BAAkB,GAAE,EAAE,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,MAAM,IAAA,4BAAmB,GAAE,CAAC;IAChD,IAAI,WAAW,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QACjD,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;SAAM,IAAI,WAAW,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,oCAAoC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;QACvE,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QACjD,QAAQ,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;SAAM,CAAC;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW,CAAC;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC7C,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,+BAA+B,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,SAAS,EAAE,CAAC,CAAC;QAChG,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,qBAAqB,YAAY,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,6DAA6D;QAC7D,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAA,8BAAiB,EAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAA,6BAAgB,EAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,iEAAiE;IACjE,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,IAAA,uBAAY,GAAE,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACtC,4DAA4D;QAC5D,0BAA0B;QAC1B,OAAO,CAAC,GAAG,CAAC,OAAO,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,iEAAiE;IACjE,4DAA4D;IAC5D,kEAAkE;IAClE,MAAM,aAAa,GAAG,IAAA,uBAAY,GAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,oBAAU,EAAC,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IACjH,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,eAAe,GAAG,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QAClB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACtB,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initCommand = initCommand;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_client_1 = require("../lib/api-client");
|
|
6
|
+
const pat_store_1 = require("../lib/pat-store");
|
|
7
|
+
const detector_1 = require("../lib/agents/detector");
|
|
8
|
+
const providers_1 = require("../lib/agents/providers");
|
|
9
|
+
const configure_mcp_1 = require("../lib/agents/configure-mcp");
|
|
10
|
+
const skill_copy_1 = require("../lib/skill-copy");
|
|
11
|
+
async function initCommand(opts = {}) {
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(' Ritual — scaffolding skills + connecting AI coding agents');
|
|
14
|
+
console.log('');
|
|
15
|
+
// --- 0. Handle --list (early exit, no auth required) ------------
|
|
16
|
+
if (opts.list) {
|
|
17
|
+
console.log(' Known agents:');
|
|
18
|
+
for (const p of providers_1.PROVIDERS) {
|
|
19
|
+
console.log(` ${p.id.padEnd(14)} ${p.name}`);
|
|
20
|
+
}
|
|
21
|
+
console.log('');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// --- 1. Auth check -----------------------------------------------
|
|
25
|
+
const tokenStatus = await (0, config_1.getValidAccessToken)();
|
|
26
|
+
if (tokenStatus.kind === 'not-signed-in') {
|
|
27
|
+
console.error(' ✗ Not signed in. Run `ritual login` first, then `ritual init`.');
|
|
28
|
+
console.error('');
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (tokenStatus.kind === 're-auth-required') {
|
|
33
|
+
console.error(' ✗ Your session expired. Run `ritual login` to re-authenticate.');
|
|
34
|
+
console.error(` Reason: ${tokenStatus.reason}`);
|
|
35
|
+
console.error('');
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// --- 2. Detect agents (or pick a specific one) -------------------
|
|
40
|
+
let targets;
|
|
41
|
+
if (opts.agent) {
|
|
42
|
+
const provider = (0, providers_1.findProviderById)(opts.agent);
|
|
43
|
+
if (!provider) {
|
|
44
|
+
console.error(` ✗ Unknown agent: ${opts.agent}`);
|
|
45
|
+
console.error(' Run `ritual init --list` to see known agents.');
|
|
46
|
+
console.error('');
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Explicit `--agent` overrides detection — the user knows what
|
|
51
|
+
// they want, don't make them install/symlink the binary just
|
|
52
|
+
// to pass detection.
|
|
53
|
+
targets = [
|
|
54
|
+
{
|
|
55
|
+
id: provider.id,
|
|
56
|
+
name: provider.name,
|
|
57
|
+
detected: true,
|
|
58
|
+
provider,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const all = (0, detector_1.detectAgents)();
|
|
64
|
+
const detected = all.filter((a) => a.detected);
|
|
65
|
+
if (detected.length === 0) {
|
|
66
|
+
// Nothing detected → default to Claude Code. This matches
|
|
67
|
+
// legacy behavior and matches the documented MCP launch
|
|
68
|
+
// partner. User can override with `--agent <id>`.
|
|
69
|
+
const claudeProvider = (0, providers_1.findProviderById)('claude-code');
|
|
70
|
+
targets = [
|
|
71
|
+
{
|
|
72
|
+
id: claudeProvider.id,
|
|
73
|
+
name: claudeProvider.name,
|
|
74
|
+
detected: false,
|
|
75
|
+
provider: claudeProvider,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
console.log(' No coding agents detected on this machine.');
|
|
79
|
+
console.log(` Defaulting to ${claudeProvider.name}. Use --agent <id> to choose explicitly.`);
|
|
80
|
+
console.log('');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
targets = detected;
|
|
84
|
+
console.log(` Detected ${detected.length} agent(s): ${detected.map((d) => d.name).join(', ')}`);
|
|
85
|
+
console.log('');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// --- 3. Mint a long-lived PAT for agents to use ------------------
|
|
89
|
+
const issuer = tokenStatus.creds.issuer;
|
|
90
|
+
const api = new api_client_1.ApiClient({
|
|
91
|
+
issuer,
|
|
92
|
+
accessToken: tokenStatus.accessToken,
|
|
93
|
+
});
|
|
94
|
+
const mcpUrl = (0, api_client_1.mcpUrlFromIssuer)(issuer);
|
|
95
|
+
let pat;
|
|
96
|
+
try {
|
|
97
|
+
pat = await (0, pat_store_1.mintAgentPat)({ api });
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error(` ✗ Could not mint access token: ${err.message}`);
|
|
101
|
+
console.error('');
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.log(` ✓ Minted ${pat.name}`);
|
|
106
|
+
console.log(` Manage tokens at: ${webAppUrlFromIssuer(issuer)}/settings/tokens`);
|
|
107
|
+
console.log('');
|
|
108
|
+
// --- 4. Copy skills + register MCP for each target agent ---------
|
|
109
|
+
const projectDir = opts.cwd ?? process.cwd();
|
|
110
|
+
const copyResults = [];
|
|
111
|
+
const registrationResults = [];
|
|
112
|
+
for (const t of targets) {
|
|
113
|
+
copyResults.push((0, skill_copy_1.copySkillsForProvider)(t.provider, projectDir));
|
|
114
|
+
registrationResults.push((0, configure_mcp_1.configureMcpForAgent)(t.provider, { url: mcpUrl, token: pat.plaintextToken }));
|
|
115
|
+
}
|
|
116
|
+
// --- 5. Summary --------------------------------------------------
|
|
117
|
+
console.log(' Per-agent results:');
|
|
118
|
+
for (const t of targets) {
|
|
119
|
+
const copy = copyResults.find((r) => r.provider.id === t.provider.id);
|
|
120
|
+
const reg = registrationResults.find((r) => r.provider.id === t.provider.id);
|
|
121
|
+
const skillSummary = copy.copied > 0
|
|
122
|
+
? `${copy.copied} skill(s) → ${t.provider.projectSkillDir}/`
|
|
123
|
+
: `skills skipped (${copy.reason ?? 'unknown'})`;
|
|
124
|
+
const mcpSummary = reg.success
|
|
125
|
+
? `MCP server registered`
|
|
126
|
+
: `MCP register failed: ${reg.reason ?? 'unknown'}`;
|
|
127
|
+
console.log(` ${t.provider.name}`);
|
|
128
|
+
console.log(` ${skillSummary}`);
|
|
129
|
+
console.log(` ${mcpSummary}`);
|
|
130
|
+
}
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log(' Next: restart your AI coding agent so it picks up the new MCP server.');
|
|
133
|
+
console.log(' In your agent, try: /ritual build <feature>');
|
|
134
|
+
console.log('');
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Mirror of `apiBaseFromIssuer` but for the web app. Used in the
|
|
138
|
+
* "manage tokens at <url>" hint above.
|
|
139
|
+
*
|
|
140
|
+
* dev: https://dev.ritualapp.cloud
|
|
141
|
+
* prod: https://app.ritualapp.cloud (matches the legacy domain)
|
|
142
|
+
*/
|
|
143
|
+
function webAppUrlFromIssuer(issuer) {
|
|
144
|
+
try {
|
|
145
|
+
const u = new URL(issuer);
|
|
146
|
+
const host = u.host;
|
|
147
|
+
if (host.startsWith('auth.dev.'))
|
|
148
|
+
return 'https://dev.ritualapp.cloud';
|
|
149
|
+
if (host === 'auth.ritualapp.cloud')
|
|
150
|
+
return 'https://app.ritualapp.cloud';
|
|
151
|
+
// Self-hosted / enterprise — replace auth. with app.
|
|
152
|
+
return `https://${host.replace(/^auth\./, 'app.')}`;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return 'https://app.ritualapp.cloud';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loginCommand = loginCommand;
|
|
7
|
+
const open_1 = __importDefault(require("open"));
|
|
8
|
+
const oidc_1 = require("../lib/oidc");
|
|
9
|
+
const config_1 = require("../lib/config");
|
|
10
|
+
const DEFAULT_ISSUER = process.env.RITUAL_KEYCLOAK_URL ?? 'https://auth.ritualapp.cloud/realms/ritual';
|
|
11
|
+
const DEFAULT_CLIENT_ID = process.env.RITUAL_KEYCLOAK_CLIENT_ID ?? 'ritual-cli';
|
|
12
|
+
const DEFAULT_SCOPE = 'openid profile email ritual:read ritual:write';
|
|
13
|
+
/**
|
|
14
|
+
* `ritual login` — browser-based Auth Code + PKCE flow against
|
|
15
|
+
* Keycloak. On success, stores the access + refresh tokens in
|
|
16
|
+
* `~/.config/ritual/credentials.json` for subsequent commands.
|
|
17
|
+
*
|
|
18
|
+
* Defaults point at production (`auth.ritualapp.cloud`); override via
|
|
19
|
+
* `--issuer https://auth.dev.ritualapp.cloud/realms/ritual` for dev,
|
|
20
|
+
* or set `RITUAL_KEYCLOAK_URL` env var.
|
|
21
|
+
*/
|
|
22
|
+
async function loginCommand(options) {
|
|
23
|
+
const issuer = options.issuer ?? DEFAULT_ISSUER;
|
|
24
|
+
const clientId = options.clientId ?? DEFAULT_CLIENT_ID;
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(' Ritual — sign in');
|
|
27
|
+
console.log('');
|
|
28
|
+
let tokenSet;
|
|
29
|
+
try {
|
|
30
|
+
tokenSet = await (0, oidc_1.performLoginFlow)({ issuer, clientId, scope: DEFAULT_SCOPE }, (url) => (0, open_1.default)(url), (msg) => console.log(` ${msg}`));
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
console.error('');
|
|
34
|
+
console.error(` ✗ Login failed: ${err.message}`);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const claims = (0, oidc_1.decodeJwtPayloadUnsafe)(tokenSet.access_token);
|
|
39
|
+
const creds = {
|
|
40
|
+
version: 1,
|
|
41
|
+
issuer,
|
|
42
|
+
clientId,
|
|
43
|
+
tokenSet: {
|
|
44
|
+
access_token: tokenSet.access_token,
|
|
45
|
+
refresh_token: tokenSet.refresh_token,
|
|
46
|
+
expires_at: Math.floor(Date.now() / 1000) + tokenSet.expires_in,
|
|
47
|
+
id_token: tokenSet.id_token,
|
|
48
|
+
scope: tokenSet.scope,
|
|
49
|
+
},
|
|
50
|
+
user: claims
|
|
51
|
+
? { sub: claims.sub, email: claims.email ?? claims.preferred_username }
|
|
52
|
+
: undefined,
|
|
53
|
+
};
|
|
54
|
+
(0, config_1.saveCredentials)(creds);
|
|
55
|
+
const display = creds.user?.email ?? creds.user?.sub ?? 'unknown';
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(` ✓ Signed in as ${display}`);
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log(' Your AI coding agent can now connect to Ritual via MCP.');
|
|
60
|
+
console.log(' Run `ritual whoami` to confirm session details.');
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":";;;;;AAuBA,oCAkDC;AAzED,gDAAwB;AACxB,sCAAuE;AACvE,0CAAqE;AAOrE,MAAM,cAAc,GACnB,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,4CAA4C,CAAC;AACjF,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,YAAY,CAAC;AAChF,MAAM,aAAa,GAAG,+CAA+C,CAAC;AAEtE;;;;;;;;GAQG;AACI,KAAK,UAAU,YAAY,CAAC,OAAqB;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC;IAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IAEvD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACJ,QAAQ,GAAG,MAAM,IAAA,uBAAgB,EAChC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,EAC1C,CAAC,GAAG,EAAE,EAAE,CAAC,IAAA,cAAI,EAAC,GAAG,CAAC,EAClB,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAChC,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,qBAAsB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACR,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,6BAAsB,EACpC,QAAQ,CAAC,YAAY,CACrB,CAAC;IAEF,MAAM,KAAK,GAAmB;QAC7B,OAAO,EAAE,CAAC;QACV,MAAM;QACN,QAAQ;QACR,QAAQ,EAAE;YACT,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,aAAa,EAAE,QAAQ,CAAC,aAAa;YACrC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,UAAU;YAC/D,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;SACrB;QACD,IAAI,EAAE,MAAM;YACX,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,kBAAkB,EAAE;YACvE,CAAC,CAAC,SAAS;KACZ,CAAC;IACF,IAAA,wBAAe,EAAC,KAAK,CAAC,CAAC;IAEvB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,SAAS,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logoutCommand = logoutCommand;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
/**
|
|
6
|
+
* `ritual logout` — clears local credentials. Does NOT call Keycloak's
|
|
7
|
+
* end-session endpoint — that's a browser-side concept; clearing local
|
|
8
|
+
* tokens is what matters from a CLI perspective. The user can sign out
|
|
9
|
+
* of the realm session itself via the web app.
|
|
10
|
+
*/
|
|
11
|
+
async function logoutCommand() {
|
|
12
|
+
const had = (0, config_1.loadCredentials)();
|
|
13
|
+
(0, config_1.clearCredentials)();
|
|
14
|
+
console.log('');
|
|
15
|
+
if (had) {
|
|
16
|
+
const display = had.user?.email ?? had.user?.sub ?? 'unknown';
|
|
17
|
+
console.log(` ✓ Signed out (${display}).`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(' No active session — nothing to clear.');
|
|
21
|
+
}
|
|
22
|
+
console.log(` Credentials path: ${(0, config_1.getCredentialsPath)()}`);
|
|
23
|
+
console.log('');
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=logout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":";;AAQA,sCAYC;AApBD,0CAAsF;AAEtF;;;;;GAKG;AACI,KAAK,UAAU,aAAa;IAClC,MAAM,GAAG,GAAG,IAAA,wBAAe,GAAE,CAAC;IAC9B,IAAA,yBAAgB,GAAE,CAAC;IACnB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,IAAI,GAAG,EAAE,CAAC;QACT,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,SAAS,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,mBAAmB,OAAO,IAAI,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAA,2BAAkB,GAAE,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refreshCommand = refreshCommand;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
/**
|
|
6
|
+
* `ritual refresh` — force-refresh the cached access token using the
|
|
7
|
+
* stored refresh token. Mostly useful for scripting + debugging
|
|
8
|
+
* (e.g. "I want to confirm my refresh token still works without
|
|
9
|
+
* waiting for the access token to expire").
|
|
10
|
+
*
|
|
11
|
+
* Most subcommands don't need this — they call `getValidAccessToken()`
|
|
12
|
+
* which refreshes lazily on demand.
|
|
13
|
+
*/
|
|
14
|
+
async function refreshCommand() {
|
|
15
|
+
console.log('');
|
|
16
|
+
const status = await (0, config_1.getValidAccessToken)();
|
|
17
|
+
if (status.kind === 'not-signed-in') {
|
|
18
|
+
console.log(' Not signed in. Run `ritual login` to authenticate.');
|
|
19
|
+
console.log('');
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (status.kind === 're-auth-required') {
|
|
24
|
+
console.log(' ✗ Refresh failed.');
|
|
25
|
+
console.log(` Reason: ${status.reason}`);
|
|
26
|
+
console.log(' Run `ritual login` to sign in again.');
|
|
27
|
+
console.log(` Credentials path: ${(0, config_1.getCredentialsPath)()}`);
|
|
28
|
+
console.log('');
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const display = status.creds.user?.email ?? status.creds.user?.sub ?? 'unknown';
|
|
33
|
+
const expiresInSec = status.creds.tokenSet.expires_at - Math.floor(Date.now() / 1000);
|
|
34
|
+
if (status.refreshed) {
|
|
35
|
+
console.log(` ✓ Refreshed token for ${display} (valid for ${expiresInSec}s)`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
console.log(` Token still fresh for ${display} (${expiresInSec}s remaining) — no refresh needed.`);
|
|
39
|
+
}
|
|
40
|
+
console.log('');
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/commands/refresh.ts"],"names":[],"mappings":";;AAWA,wCAgCC;AA3CD,0CAAwE;AAExE;;;;;;;;GAQG;AACI,KAAK,UAAU,cAAc;IACnC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,MAAM,GAAG,MAAM,IAAA,4BAAmB,GAAE,CAAC;IAE3C,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACR,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAA,2BAAkB,GAAE,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACR,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,SAAS,CAAC;IAChF,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAEtF,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,2BAA2B,OAAO,eAAe,YAAY,IAAI,CAAC,CAAC;IAChF,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,GAAG,CACV,2BAA2B,OAAO,KAAK,YAAY,mCAAmC,CACtF,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.whoamiCommand = whoamiCommand;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
/**
|
|
6
|
+
* `ritual whoami` — print the active session's identity + token
|
|
7
|
+
* status. Calls `getValidAccessToken()` so an expired access token
|
|
8
|
+
* is transparently refreshed first; the user sees current state, not
|
|
9
|
+
* stale state.
|
|
10
|
+
*
|
|
11
|
+
* Three terminal states:
|
|
12
|
+
* - signed-in: print identity + token expiry
|
|
13
|
+
* - not-signed-in: suggest `ritual login`
|
|
14
|
+
* - re-auth-required: refresh failed; clear hint to re-login
|
|
15
|
+
*/
|
|
16
|
+
async function whoamiCommand() {
|
|
17
|
+
console.log('');
|
|
18
|
+
const status = await (0, config_1.getValidAccessToken)();
|
|
19
|
+
if (status.kind === 'not-signed-in') {
|
|
20
|
+
console.log(' Not signed in. Run `ritual login` to authenticate.');
|
|
21
|
+
console.log('');
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (status.kind === 're-auth-required') {
|
|
26
|
+
console.log(' ✗ Session expired and refresh failed.');
|
|
27
|
+
console.log(` Reason: ${status.reason}`);
|
|
28
|
+
console.log(' Run `ritual login` to sign in again.');
|
|
29
|
+
console.log(` Credentials path: ${(0, config_1.getCredentialsPath)()}`);
|
|
30
|
+
console.log('');
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const { creds, refreshed } = status;
|
|
35
|
+
const display = creds.user?.email ?? creds.user?.sub ?? 'unknown';
|
|
36
|
+
const issuerHost = (() => {
|
|
37
|
+
try {
|
|
38
|
+
return new URL(creds.issuer).host;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return creds.issuer;
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
const expiresInSec = creds.tokenSet.expires_at - Math.floor(Date.now() / 1000);
|
|
45
|
+
console.log(` Signed in as: ${display}`);
|
|
46
|
+
console.log(` Issuer: ${issuerHost}`);
|
|
47
|
+
console.log(` Client: ${creds.clientId}`);
|
|
48
|
+
console.log(` Scopes: ${creds.tokenSet.scope ?? '(none)'}`);
|
|
49
|
+
console.log(` Access token: ✓ valid (expires in ${expiresInSec > 0 ? `${expiresInSec}s` : 'expired'})${refreshed ? ' — auto-refreshed' : ''}`);
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":";;AAaA,sCAwCC;AArDD,0CAAwE;AAExE;;;;;;;;;;GAUG;AACI,KAAK,UAAU,aAAa;IAClC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,MAAM,GAAG,MAAM,IAAA,4BAAmB,GAAE,CAAC;IAE3C,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACR,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAA,2BAAkB,GAAE,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACR,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,SAAS,CAAC;IAClE,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE;QACxB,IAAI,CAAC;YACJ,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,KAAK,CAAC,MAAM,CAAC;QACrB,CAAC;IACF,CAAC,CAAC,EAAE,CAAC;IACL,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAE/E,OAAO,CAAC,GAAG,CAAC,mBAAmB,OAAO,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,mBAAmB,UAAU,EAAE,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,mBAAmB,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CACV,uCAAuC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,EAAE,CAClI,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const login_1 = require("./commands/login");
|
|
6
|
+
const logout_1 = require("./commands/logout");
|
|
7
|
+
const whoami_1 = require("./commands/whoami");
|
|
8
|
+
const refresh_1 = require("./commands/refresh");
|
|
9
|
+
const init_1 = require("./commands/init");
|
|
10
|
+
const doctor_1 = require("./commands/doctor");
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('ritual')
|
|
14
|
+
.description('Ritual CLI — connect AI coding agents to Ritual Cloud. ' +
|
|
15
|
+
'Scaffold skills, register MCP servers, manage sessions.')
|
|
16
|
+
.version('0.3.0');
|
|
17
|
+
// `init` is the headline command: scaffold + register against every
|
|
18
|
+
// detected agent. Listed first so `ritual --help` surfaces it.
|
|
19
|
+
program
|
|
20
|
+
.command('init')
|
|
21
|
+
.description('Scaffold Ritual skills + register MCP server with every detected AI coding agent')
|
|
22
|
+
.option('--agent <id>', 'Restrict to one agent (e.g. claude-code, cursor, kiro)')
|
|
23
|
+
.option('--list', 'List known agents and exit')
|
|
24
|
+
.action(init_1.initCommand);
|
|
25
|
+
program
|
|
26
|
+
.command('login')
|
|
27
|
+
.description('Authenticate with Ritual via your browser')
|
|
28
|
+
.option('--issuer <url>', 'OIDC issuer (defaults to https://auth.ritualapp.cloud/realms/ritual or RITUAL_KEYCLOAK_URL env)')
|
|
29
|
+
.option('--client-id <id>', 'OIDC client id (defaults to "ritual-cli" or RITUAL_KEYCLOAK_CLIENT_ID env)')
|
|
30
|
+
.action(login_1.loginCommand);
|
|
31
|
+
program
|
|
32
|
+
.command('logout')
|
|
33
|
+
.description('Clear local credentials')
|
|
34
|
+
.action(logout_1.logoutCommand);
|
|
35
|
+
program
|
|
36
|
+
.command('whoami')
|
|
37
|
+
.description('Show current session info (auto-refreshes token if expired)')
|
|
38
|
+
.action(whoami_1.whoamiCommand);
|
|
39
|
+
program
|
|
40
|
+
.command('refresh')
|
|
41
|
+
.description('Force-refresh the cached access token using the stored refresh token')
|
|
42
|
+
.action(refresh_1.refreshCommand);
|
|
43
|
+
program
|
|
44
|
+
.command('doctor')
|
|
45
|
+
.description('Sanity check the CLI environment: credentials, endpoints, detected agents')
|
|
46
|
+
.action(doctor_1.doctorCommand);
|
|
47
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
48
|
+
console.error(`\n ✗ ${err.message}\n`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,yCAAoC;AACpC,4CAAgD;AAChD,8CAAkD;AAClD,8CAAkD;AAClD,gDAAoD;AACpD,0CAA8C;AAC9C,8CAAkD;AAElD,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACL,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CACX,yDAAyD;IACxD,yDAAyD,CAC1D;KACA,OAAO,CAAC,OAAO,CAAC,CAAC;AAEnB,oEAAoE;AACpE,+DAA+D;AAC/D,OAAO;KACL,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,kFAAkF,CAAC;KAC/F,MAAM,CAAC,cAAc,EAAE,wDAAwD,CAAC;KAChF,MAAM,CAAC,QAAQ,EAAE,4BAA4B,CAAC;KAC9C,MAAM,CAAC,kBAAW,CAAC,CAAC;AAEtB,OAAO;KACL,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,2CAA2C,CAAC;KACxD,MAAM,CACN,gBAAgB,EAChB,iGAAiG,CACjG;KACA,MAAM,CACN,kBAAkB,EAClB,4EAA4E,CAC5E;KACA,MAAM,CAAC,oBAAY,CAAC,CAAC;AAEvB,OAAO;KACL,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,yBAAyB,CAAC;KACtC,MAAM,CAAC,sBAAa,CAAC,CAAC;AAExB,OAAO;KACL,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,6DAA6D,CAAC;KAC1E,MAAM,CAAC,sBAAa,CAAC,CAAC;AAExB,OAAO;KACL,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,sEAAsE,CAAC;KACnF,MAAM,CAAC,wBAAc,CAAC,CAAC;AAEzB,OAAO;KACL,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,2EAA2E,CAAC;KACxF,MAAM,CAAC,sBAAa,CAAC,CAAC;AAExB,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACrD,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;IACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"}
|