@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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureMcpForAgent = configureMcpForAgent;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
/**
|
|
8
|
+
* MCP server registration for one agent.
|
|
9
|
+
*
|
|
10
|
+
* Today we register one server, named `ritual`, against
|
|
11
|
+
* `https://mcp.ritualapp.cloud/mcp` (prod) or
|
|
12
|
+
* `https://mcp.dev.ritualapp.cloud/mcp` (dev) — using a long-lived
|
|
13
|
+
* Personal Access Token as the bearer.
|
|
14
|
+
*
|
|
15
|
+
* Why a PAT and not the user's OAuth access token:
|
|
16
|
+
* The OAuth access token from `ritual login` expires every 5-15
|
|
17
|
+
* minutes. If we wrote that into the agent's config, the agent
|
|
18
|
+
* would 401 within minutes and the user would have to re-init.
|
|
19
|
+
* PATs are designed for exactly this — long-lived, scoped,
|
|
20
|
+
* revocable per-device tokens. Mint once, agent uses for months.
|
|
21
|
+
*
|
|
22
|
+
* The PAT is created with a name like "Ritual CLI — {hostname}" so
|
|
23
|
+
* the user can see in the management UI which device it's bound to
|
|
24
|
+
* and revoke per-device if they suspect compromise.
|
|
25
|
+
*/
|
|
26
|
+
const SERVER_NAME = 'ritual';
|
|
27
|
+
/**
|
|
28
|
+
* Register an MCP server with one agent.
|
|
29
|
+
*
|
|
30
|
+
* Returns a result object rather than throwing because the CLI calls
|
|
31
|
+
* this in a loop across all detected agents — one failure shouldn't
|
|
32
|
+
* abort the rest. The user can re-run for the failed agent.
|
|
33
|
+
*/
|
|
34
|
+
function configureMcpForAgent(provider, registration) {
|
|
35
|
+
try {
|
|
36
|
+
if (provider.mcpConfigMethod === 'cli-command') {
|
|
37
|
+
configureViaCliCommand(provider, registration);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
configureViaJsonFile(provider, registration);
|
|
41
|
+
}
|
|
42
|
+
return { provider, success: true };
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return {
|
|
46
|
+
provider,
|
|
47
|
+
success: false,
|
|
48
|
+
reason: err.message,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Claude Code path: shell out to `claude mcp add-json --scope user`.
|
|
54
|
+
*
|
|
55
|
+
* We re-register idempotently by removing-then-adding (the CLI errors
|
|
56
|
+
* on a duplicate name). The `try/catch` around remove is intentional —
|
|
57
|
+
* "not found" is fine; we just want a clean slate.
|
|
58
|
+
*/
|
|
59
|
+
function configureViaCliCommand(_provider, registration) {
|
|
60
|
+
const config = {
|
|
61
|
+
type: 'http',
|
|
62
|
+
url: registration.url,
|
|
63
|
+
headers: { Authorization: `Bearer ${registration.token}` },
|
|
64
|
+
};
|
|
65
|
+
// 1. Remove any existing entry, swallowing errors.
|
|
66
|
+
try {
|
|
67
|
+
(0, node_child_process_1.execSync)(`claude mcp remove ${SERVER_NAME} --scope user`, { stdio: 'pipe' });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* not found — fine */
|
|
71
|
+
}
|
|
72
|
+
// 2. Add the fresh entry. Escape the JSON for the shell.
|
|
73
|
+
// Using single-quote wrap is safe because the JSON contains no
|
|
74
|
+
// single quotes (object keys + string values are all double-quoted
|
|
75
|
+
// in JSON-stringify output).
|
|
76
|
+
const json = JSON.stringify(config);
|
|
77
|
+
(0, node_child_process_1.execSync)(`claude mcp add-json --scope user ${SERVER_NAME} '${json}'`, {
|
|
78
|
+
stdio: 'pipe',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* JSON-file path: read existing config (if any), merge in our server
|
|
83
|
+
* entry, write back.
|
|
84
|
+
*
|
|
85
|
+
* We tolerate corrupted JSON by treating it as an empty file. The
|
|
86
|
+
* user's other (broken) config is preserved on disk under a `.bak`
|
|
87
|
+
* sibling so we never silently destroy state we don't understand.
|
|
88
|
+
*/
|
|
89
|
+
function configureViaJsonFile(provider, registration) {
|
|
90
|
+
if (!provider.mcpConfigPath) {
|
|
91
|
+
throw new Error(`${provider.name} is json-file but has no mcpConfigPath`);
|
|
92
|
+
}
|
|
93
|
+
const filePath = provider.mcpConfigPath;
|
|
94
|
+
const serversKey = provider.mcpConfigKey ?? 'mcpServers';
|
|
95
|
+
let config = {};
|
|
96
|
+
if ((0, node_fs_1.existsSync)(filePath)) {
|
|
97
|
+
const raw = (0, node_fs_1.readFileSync)(filePath, 'utf-8');
|
|
98
|
+
try {
|
|
99
|
+
config = JSON.parse(raw);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Preserve the broken file before we overwrite it.
|
|
103
|
+
(0, node_fs_1.writeFileSync)(`${filePath}.bak`, raw);
|
|
104
|
+
config = {};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (typeof config[serversKey] !== 'object' || config[serversKey] === null) {
|
|
108
|
+
config[serversKey] = {};
|
|
109
|
+
}
|
|
110
|
+
const servers = config[serversKey];
|
|
111
|
+
servers[SERVER_NAME] = {
|
|
112
|
+
type: 'http',
|
|
113
|
+
url: registration.url,
|
|
114
|
+
headers: { Authorization: `Bearer ${registration.token}` },
|
|
115
|
+
};
|
|
116
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(filePath), { recursive: true });
|
|
117
|
+
(0, node_fs_1.writeFileSync)(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
118
|
+
// chmod 0600 — config contains a Bearer token; only the user
|
|
119
|
+
// should be able to read it. No-op on Windows.
|
|
120
|
+
try {
|
|
121
|
+
(0, node_fs_1.chmodSync)(filePath, 0o600);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
/* ignore */
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=configure-mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configure-mcp.js","sourceRoot":"","sources":["../../../src/lib/agents/configure-mcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectAgents = detectAgents;
|
|
4
|
+
exports.detectedAgents = detectedAgents;
|
|
5
|
+
const providers_1 = require("./providers");
|
|
6
|
+
/**
|
|
7
|
+
* Probe every known agent. Returns the full list in the order they
|
|
8
|
+
* appear in `PROVIDERS` (deterministic — useful for snapshot tests).
|
|
9
|
+
*
|
|
10
|
+
* Caller usually filters to `.detected === true`; we return the full
|
|
11
|
+
* list so `ritual doctor` can also report "Cursor: not detected".
|
|
12
|
+
*/
|
|
13
|
+
function detectAgents() {
|
|
14
|
+
return providers_1.PROVIDERS.map((p) => {
|
|
15
|
+
let detected;
|
|
16
|
+
try {
|
|
17
|
+
detected = p.detect();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Any thrown error during detection → treat as not detected.
|
|
21
|
+
// Detection probes touch the filesystem and shell out; we
|
|
22
|
+
// never want a transient EBUSY / EACCES to break `ritual init`.
|
|
23
|
+
detected = false;
|
|
24
|
+
}
|
|
25
|
+
return { id: p.id, name: p.name, detected, provider: p };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Just the agents we detected — convenience for `init`. */
|
|
29
|
+
function detectedAgents() {
|
|
30
|
+
return detectAgents().filter((r) => r.detected);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../../src/lib/agents/detector.ts"],"names":[],"mappings":";;AA0BA,oCAaC;AAGD,wCAEC;AA5CD,2CAA4D;AAmB5D;;;;;;GAMG;AACH,SAAgB,YAAY;IAC3B,OAAO,qBAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC1B,IAAI,QAAiB,CAAC;QACtB,IAAI,CAAC;YACJ,QAAQ,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACR,6DAA6D;YAC7D,0DAA0D;YAC1D,gEAAgE;YAChE,QAAQ,GAAG,KAAK,CAAC;QAClB,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IAC1D,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,4DAA4D;AAC5D,SAAgB,cAAc;IAC7B,OAAO,YAAY,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROVIDERS = void 0;
|
|
4
|
+
exports.findProviderById = findProviderById;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
/**
|
|
10
|
+
* Tiny helper: is `cmd` on the PATH? Used by the detectors below.
|
|
11
|
+
*
|
|
12
|
+
* We use `which`/`where` rather than spawning the agent because some
|
|
13
|
+
* agents print interactive output on `--version`, and we don't want to
|
|
14
|
+
* pollute the user's terminal during detection.
|
|
15
|
+
*/
|
|
16
|
+
function commandExists(cmd) {
|
|
17
|
+
try {
|
|
18
|
+
const which = process.platform === 'win32' ? 'where' : 'which';
|
|
19
|
+
(0, node_child_process_1.execSync)(`${which} ${cmd}`, { stdio: 'pipe' });
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The canonical agent registry. Order is significant: `ritual init`
|
|
28
|
+
* iterates this list to write skills, so agents that share parent
|
|
29
|
+
* directories (e.g. Cursor + Windsurf both use `.cursor/rules` source
|
|
30
|
+
* format) appear in their preferred order.
|
|
31
|
+
*
|
|
32
|
+
* To add a new agent: append to this array. Detection + paths are
|
|
33
|
+
* self-contained; nothing else in the CLI needs to know about it.
|
|
34
|
+
*/
|
|
35
|
+
exports.PROVIDERS = [
|
|
36
|
+
{
|
|
37
|
+
name: 'Claude Code',
|
|
38
|
+
id: 'claude-code',
|
|
39
|
+
// `claude` on PATH OR ~/.claude dir present. The directory check
|
|
40
|
+
// covers fresh installs where the user hasn't symlinked the
|
|
41
|
+
// binary into PATH yet (homebrew + manual installs both).
|
|
42
|
+
detect: () => commandExists('claude') || (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.claude')),
|
|
43
|
+
projectSkillDir: '.claude/skills',
|
|
44
|
+
packageSkillDir: 'skills/claude-code',
|
|
45
|
+
// `claude mcp add-json` writes to ~/.claude.json itself; we
|
|
46
|
+
// shell out so we don't fight its config-file format.
|
|
47
|
+
mcpConfigMethod: 'cli-command',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Cursor',
|
|
51
|
+
id: 'cursor',
|
|
52
|
+
detect: () => (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.cursor')),
|
|
53
|
+
projectSkillDir: '.cursor/rules',
|
|
54
|
+
packageSkillDir: 'skills/cursor',
|
|
55
|
+
mcpConfigMethod: 'json-file',
|
|
56
|
+
mcpConfigPath: (0, node_path_1.join)((0, node_os_1.homedir)(), '.cursor', 'mcp.json'),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Windsurf',
|
|
60
|
+
id: 'windsurf',
|
|
61
|
+
// Windsurf is Codeium's IDE; the dotdir is `.codeium`.
|
|
62
|
+
detect: () => (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.codeium')),
|
|
63
|
+
projectSkillDir: '.windsurf/rules',
|
|
64
|
+
// Windsurf accepts the same rules format Cursor does, so we ship
|
|
65
|
+
// the same bundle.
|
|
66
|
+
packageSkillDir: 'skills/cursor',
|
|
67
|
+
mcpConfigMethod: 'json-file',
|
|
68
|
+
mcpConfigPath: (0, node_path_1.join)((0, node_os_1.homedir)(), '.codeium', 'windsurf', 'mcp_config.json'),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'Kiro',
|
|
72
|
+
id: 'kiro',
|
|
73
|
+
detect: () => (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.kiro')) || commandExists('kiro'),
|
|
74
|
+
projectSkillDir: '.kiro/skills',
|
|
75
|
+
packageSkillDir: 'skills/kiro',
|
|
76
|
+
mcpConfigMethod: 'json-file',
|
|
77
|
+
mcpConfigPath: (0, node_path_1.join)((0, node_os_1.homedir)(), '.kiro', 'mcp.json'),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Gemini CLI',
|
|
81
|
+
id: 'gemini',
|
|
82
|
+
detect: () => (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.gemini')) || commandExists('gemini'),
|
|
83
|
+
projectSkillDir: '.gemini/skills',
|
|
84
|
+
packageSkillDir: 'skills/gemini',
|
|
85
|
+
mcpConfigMethod: 'json-file',
|
|
86
|
+
mcpConfigPath: (0, node_path_1.join)((0, node_os_1.homedir)(), '.gemini', 'mcp.json'),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'VS Code (Copilot)',
|
|
90
|
+
id: 'vscode',
|
|
91
|
+
// `code` CLI or ~/.vscode dir. VS Code's MCP support comes via
|
|
92
|
+
// the GitHub Copilot Chat extension; the config path is the
|
|
93
|
+
// same regardless.
|
|
94
|
+
detect: () => commandExists('code') || (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.vscode')),
|
|
95
|
+
projectSkillDir: '.github/copilot/skills',
|
|
96
|
+
packageSkillDir: 'skills/vscode',
|
|
97
|
+
mcpConfigMethod: 'json-file',
|
|
98
|
+
mcpConfigPath: (0, node_path_1.join)((0, node_os_1.homedir)(), '.vscode', 'mcp.json'),
|
|
99
|
+
// VS Code's quirk: top-level key is `servers`, not `mcpServers`.
|
|
100
|
+
mcpConfigKey: 'servers',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'Codex',
|
|
104
|
+
id: 'codex',
|
|
105
|
+
// Codex (the OpenAI coding agent — not the deprecated model)
|
|
106
|
+
// reads `.agents/` config from the user's home or project.
|
|
107
|
+
detect: () => (0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.codex')) || commandExists('codex'),
|
|
108
|
+
projectSkillDir: '.agents/skills',
|
|
109
|
+
packageSkillDir: 'skills/codex',
|
|
110
|
+
mcpConfigMethod: 'json-file',
|
|
111
|
+
mcpConfigPath: (0, node_path_1.join)((0, node_os_1.homedir)(), '.codex', 'mcp.json'),
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
/** Look up a provider by its kebab-case id, or undefined if unknown. */
|
|
115
|
+
function findProviderById(id) {
|
|
116
|
+
return exports.PROVIDERS.find((p) => p.id === id);
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../../src/lib/agents/providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tiny HTTP client for calling the Ritual REST API from the CLI.
|
|
4
|
+
*
|
|
5
|
+
* We don't pull in axios / undici / etc. — global `fetch` (Node 20+,
|
|
6
|
+
* already in `package.json` engines) is enough for the handful of
|
|
7
|
+
* endpoints the CLI calls.
|
|
8
|
+
*
|
|
9
|
+
* The base URL is derived from the user's signed-in issuer so the dev
|
|
10
|
+
* CLI talks to dev API and prod talks to prod, without a second
|
|
11
|
+
* `--api-url` flag for the user to keep in sync.
|
|
12
|
+
*
|
|
13
|
+
* issuer: https://auth.dev.ritualapp.cloud/realms/ritual
|
|
14
|
+
* apiBase: https://api.dev.ritualapp.cloud
|
|
15
|
+
*
|
|
16
|
+
* issuer: https://auth.ritualapp.cloud/realms/ritual
|
|
17
|
+
* apiBase: https://api.ritualapp.cloud
|
|
18
|
+
*
|
|
19
|
+
* Override path for self-hosted: `RITUAL_API_URL` env var wins over
|
|
20
|
+
* the derivation above.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.ApiClient = exports.ApiError = void 0;
|
|
24
|
+
exports.apiBaseFromIssuer = apiBaseFromIssuer;
|
|
25
|
+
exports.mcpUrlFromIssuer = mcpUrlFromIssuer;
|
|
26
|
+
class ApiError extends Error {
|
|
27
|
+
status;
|
|
28
|
+
body;
|
|
29
|
+
constructor(status, body) {
|
|
30
|
+
super(`api error ${status}: ${body.slice(0, 200)}`);
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.body = body;
|
|
33
|
+
this.name = 'ApiError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.ApiError = ApiError;
|
|
37
|
+
/**
|
|
38
|
+
* Map a Keycloak issuer URL to the matching Ritual API base URL.
|
|
39
|
+
*
|
|
40
|
+
* - dev: api.dev.ritualapp.cloud
|
|
41
|
+
* - prod: api.ritualapp.cloud
|
|
42
|
+
* - else: pulled from the issuer host, replacing `auth.` with `api.`
|
|
43
|
+
* (lets self-hosted enterprise installs work without env var).
|
|
44
|
+
*/
|
|
45
|
+
function apiBaseFromIssuer(issuer) {
|
|
46
|
+
if (process.env.RITUAL_API_URL) {
|
|
47
|
+
return process.env.RITUAL_API_URL.replace(/\/$/, '');
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const u = new URL(issuer);
|
|
51
|
+
// auth.* → api.* (covers auth.ritualapp.cloud and
|
|
52
|
+
// auth.dev.ritualapp.cloud, plus enterprise patterns like
|
|
53
|
+
// auth.acme.com → api.acme.com).
|
|
54
|
+
const host = u.host.replace(/^auth\./, 'api.');
|
|
55
|
+
return `https://${host}`;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Bad issuer URL — fall back to prod and let the request fail
|
|
59
|
+
// loudly with a real error message instead of crashing on URL
|
|
60
|
+
// parsing.
|
|
61
|
+
return 'https://api.ritualapp.cloud';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
class ApiClient {
|
|
65
|
+
baseUrl;
|
|
66
|
+
accessToken;
|
|
67
|
+
constructor(cfg) {
|
|
68
|
+
this.baseUrl = cfg.apiUrlOverride ?? apiBaseFromIssuer(cfg.issuer);
|
|
69
|
+
this.accessToken = cfg.accessToken;
|
|
70
|
+
}
|
|
71
|
+
/** Convenience: the base URL we're calling. Surfaced for `doctor`. */
|
|
72
|
+
getBaseUrl() {
|
|
73
|
+
return this.baseUrl;
|
|
74
|
+
}
|
|
75
|
+
async post(path, body) {
|
|
76
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify(body),
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
throw new ApiError(res.status, await res.text());
|
|
86
|
+
}
|
|
87
|
+
return (await res.json());
|
|
88
|
+
}
|
|
89
|
+
async get(path) {
|
|
90
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
91
|
+
headers: { Authorization: `Bearer ${this.accessToken}` },
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
throw new ApiError(res.status, await res.text());
|
|
95
|
+
}
|
|
96
|
+
return (await res.json());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.ApiClient = ApiClient;
|
|
100
|
+
/**
|
|
101
|
+
* Map the same issuer derivation to the MCP server URL.
|
|
102
|
+
*
|
|
103
|
+
* - dev: https://mcp.dev.ritualapp.cloud/mcp
|
|
104
|
+
* - prod: https://mcp.ritualapp.cloud/mcp
|
|
105
|
+
*
|
|
106
|
+
* Override with `RITUAL_MCP_URL` env var.
|
|
107
|
+
*/
|
|
108
|
+
function mcpUrlFromIssuer(issuer) {
|
|
109
|
+
if (process.env.RITUAL_MCP_URL)
|
|
110
|
+
return process.env.RITUAL_MCP_URL;
|
|
111
|
+
try {
|
|
112
|
+
const u = new URL(issuer);
|
|
113
|
+
const host = u.host.replace(/^auth\./, 'mcp.');
|
|
114
|
+
return `https://${host}/mcp`;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return 'https://mcp.ritualapp.cloud/mcp';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCredentialsPath = getCredentialsPath;
|
|
4
|
+
exports.loadCredentials = loadCredentials;
|
|
5
|
+
exports.saveCredentials = saveCredentials;
|
|
6
|
+
exports.clearCredentials = clearCredentials;
|
|
7
|
+
exports.hasValidCredentials = hasValidCredentials;
|
|
8
|
+
exports.getValidAccessToken = getValidAccessToken;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_os_1 = require("node:os");
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const oidc_1 = require("./oidc");
|
|
13
|
+
/**
|
|
14
|
+
* Local credentials store for the Ritual CLI.
|
|
15
|
+
*
|
|
16
|
+
* Credentials live at `~/.config/ritual/credentials.json` (XDG-friendly
|
|
17
|
+
* on Linux/macOS; uses HOME on Windows too — fine in practice). File
|
|
18
|
+
* is created with 0600 permissions so other users on a shared machine
|
|
19
|
+
* can't read it.
|
|
20
|
+
*
|
|
21
|
+
* The shape is small and stable across CLI versions:
|
|
22
|
+
*
|
|
23
|
+
* {
|
|
24
|
+
* "version": 1,
|
|
25
|
+
* "issuer": "https://auth.ritualapp.cloud/realms/ritual",
|
|
26
|
+
* "clientId": "ritual-cli",
|
|
27
|
+
* "tokenSet": {
|
|
28
|
+
* "access_token": "...",
|
|
29
|
+
* "refresh_token": "...",
|
|
30
|
+
* "expires_at": 1700000000, // unix seconds
|
|
31
|
+
* "id_token": "...",
|
|
32
|
+
* "scope": "openid profile email ritual:read ritual:write"
|
|
33
|
+
* },
|
|
34
|
+
* "user": {
|
|
35
|
+
* "sub": "kc-uuid",
|
|
36
|
+
* "email": "alice@example.com"
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* Older CLI versions reading a newer file ignore unknown fields; newer
|
|
41
|
+
* CLIs reading an older file run a tiny migration. We're at v1 so this
|
|
42
|
+
* is theoretical until v2.
|
|
43
|
+
*/
|
|
44
|
+
const CONFIG_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'ritual');
|
|
45
|
+
const CREDS_PATH = (0, node_path_1.join)(CONFIG_DIR, 'credentials.json');
|
|
46
|
+
function getCredentialsPath() {
|
|
47
|
+
return CREDS_PATH;
|
|
48
|
+
}
|
|
49
|
+
function loadCredentials() {
|
|
50
|
+
if (!(0, node_fs_1.existsSync)(CREDS_PATH))
|
|
51
|
+
return null;
|
|
52
|
+
try {
|
|
53
|
+
const raw = (0, node_fs_1.readFileSync)(CREDS_PATH, 'utf-8');
|
|
54
|
+
const parsed = JSON.parse(raw);
|
|
55
|
+
if (parsed.version !== 1)
|
|
56
|
+
return null;
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Corrupt file — treat as no credentials. User can re-login.
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function saveCredentials(creds) {
|
|
65
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(CREDS_PATH), { recursive: true });
|
|
66
|
+
(0, node_fs_1.writeFileSync)(CREDS_PATH, JSON.stringify(creds, null, 2), 'utf-8');
|
|
67
|
+
// chmod 0600 — owner read/write only. No-op on Windows but
|
|
68
|
+
// harmless. Best-effort; some FS (e.g. on CI) may not honor it.
|
|
69
|
+
try {
|
|
70
|
+
(0, node_fs_1.chmodSync)(CREDS_PATH, 0o600);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* ignore */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function clearCredentials() {
|
|
77
|
+
if ((0, node_fs_1.existsSync)(CREDS_PATH)) {
|
|
78
|
+
try {
|
|
79
|
+
(0, node_fs_1.unlinkSync)(CREDS_PATH);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
/* ignore */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** True if credentials exist AND the access token isn't expired. */
|
|
87
|
+
function hasValidCredentials() {
|
|
88
|
+
const c = loadCredentials();
|
|
89
|
+
if (!c)
|
|
90
|
+
return false;
|
|
91
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
92
|
+
return c.tokenSet.expires_at > nowSec + 30; // small buffer for clock skew
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Return a valid access token, refreshing transparently if the cached
|
|
96
|
+
* one is expired. This is the primary entry point any subcommand
|
|
97
|
+
* should use to "get my Bearer token" — it hides the refresh logic
|
|
98
|
+
* from the call site.
|
|
99
|
+
*
|
|
100
|
+
* Behavior:
|
|
101
|
+
* 1. No creds on disk → `not-signed-in`
|
|
102
|
+
* 2. Access token still fresh (>30s buffer) → return as-is
|
|
103
|
+
* 3. Access token expired but refresh token present → exchange,
|
|
104
|
+
* persist new tokens, return new access token
|
|
105
|
+
* 4. Refresh fails with 4xx → `re-auth-required` (refresh token
|
|
106
|
+
* itself is dead; nothing left but to re-login)
|
|
107
|
+
* 5. Refresh fails with 5xx/network → throw (caller can retry)
|
|
108
|
+
*/
|
|
109
|
+
async function getValidAccessToken() {
|
|
110
|
+
const creds = loadCredentials();
|
|
111
|
+
if (!creds)
|
|
112
|
+
return { kind: 'not-signed-in' };
|
|
113
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
114
|
+
if (creds.tokenSet.expires_at > nowSec + 30) {
|
|
115
|
+
return { kind: 'signed-in', accessToken: creds.tokenSet.access_token, creds, refreshed: false };
|
|
116
|
+
}
|
|
117
|
+
if (!creds.tokenSet.refresh_token) {
|
|
118
|
+
return {
|
|
119
|
+
kind: 're-auth-required',
|
|
120
|
+
reason: 'access token expired and no refresh token stored',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const fresh = await (0, oidc_1.refreshTokens)({ issuer: creds.issuer, clientId: creds.clientId }, creds.tokenSet.refresh_token);
|
|
125
|
+
const claims = (0, oidc_1.decodeJwtPayloadUnsafe)(fresh.access_token);
|
|
126
|
+
const updated = {
|
|
127
|
+
...creds,
|
|
128
|
+
tokenSet: {
|
|
129
|
+
access_token: fresh.access_token,
|
|
130
|
+
refresh_token: fresh.refresh_token ?? creds.tokenSet.refresh_token,
|
|
131
|
+
expires_at: Math.floor(Date.now() / 1000) + fresh.expires_in,
|
|
132
|
+
id_token: fresh.id_token ?? creds.tokenSet.id_token,
|
|
133
|
+
scope: fresh.scope ?? creds.tokenSet.scope,
|
|
134
|
+
},
|
|
135
|
+
user: claims
|
|
136
|
+
? { sub: claims.sub, email: claims.email ?? claims.preferred_username }
|
|
137
|
+
: creds.user,
|
|
138
|
+
};
|
|
139
|
+
saveCredentials(updated);
|
|
140
|
+
return {
|
|
141
|
+
kind: 'signed-in',
|
|
142
|
+
accessToken: fresh.access_token,
|
|
143
|
+
creds: updated,
|
|
144
|
+
refreshed: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
if (err instanceof oidc_1.RefreshTokenError && err.status >= 400 && err.status < 500) {
|
|
149
|
+
return { kind: 're-auth-required', reason: err.message };
|
|
150
|
+
}
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|