@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.
Files changed (38) hide show
  1. package/README.md +120 -0
  2. package/dist/commands/doctor.js +87 -0
  3. package/dist/commands/doctor.js.map +1 -0
  4. package/dist/commands/init.js +158 -0
  5. package/dist/commands/init.js.map +1 -0
  6. package/dist/commands/login.js +63 -0
  7. package/dist/commands/login.js.map +1 -0
  8. package/dist/commands/logout.js +25 -0
  9. package/dist/commands/logout.js.map +1 -0
  10. package/dist/commands/refresh.js +42 -0
  11. package/dist/commands/refresh.js.map +1 -0
  12. package/dist/commands/whoami.js +52 -0
  13. package/dist/commands/whoami.js.map +1 -0
  14. package/dist/index.js +51 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/lib/agents/configure-mcp.js +127 -0
  17. package/dist/lib/agents/configure-mcp.js.map +1 -0
  18. package/dist/lib/agents/detector.js +32 -0
  19. package/dist/lib/agents/detector.js.map +1 -0
  20. package/dist/lib/agents/providers.js +118 -0
  21. package/dist/lib/agents/providers.js.map +1 -0
  22. package/dist/lib/api-client.js +120 -0
  23. package/dist/lib/api-client.js.map +1 -0
  24. package/dist/lib/config.js +154 -0
  25. package/dist/lib/config.js.map +1 -0
  26. package/dist/lib/oidc.js +213 -0
  27. package/dist/lib/oidc.js.map +1 -0
  28. package/dist/lib/pat-store.js +44 -0
  29. package/dist/lib/pat-store.js.map +1 -0
  30. package/dist/lib/skill-copy.js +57 -0
  31. package/dist/lib/skill-copy.js.map +1 -0
  32. package/package.json +59 -0
  33. package/skills/claude-code/ritual/SKILL.md +271 -0
  34. package/skills/codex/ritual/SKILL.md +271 -0
  35. package/skills/cursor/ritual/SKILL.md +271 -0
  36. package/skills/gemini/ritual/SKILL.md +271 -0
  37. package/skills/kiro/ritual/SKILL.md +271 -0
  38. 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":";;;AAmLA,4CAEC;AArLD,qCAAqC;AACrC,qCAAkC;AAClC,yCAAiC;AACjC,2DAA8C;AAqE9C;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,GAAW;IACjC,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC/D,IAAA,6BAAQ,EAAC,GAAG,KAAK,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED;;;;;;;;GAQG;AACU,QAAA,SAAS,GAAoB;IACzC;QACC,IAAI,EAAE,aAAa;QACnB,EAAE,EAAE,aAAa;QACjB,iEAAiE;QACjE,4DAA4D;QAC5D,0DAA0D;QAC1D,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,CAAC,CAAC;QAC/E,eAAe,EAAE,gBAAgB;QACjC,eAAe,EAAE,oBAAoB;QACrC,4DAA4D;QAC5D,sDAAsD;QACtD,eAAe,EAAE,aAAa;KAC9B;IACD;QACC,IAAI,EAAE,QAAQ;QACd,EAAE,EAAE,QAAQ;QACZ,MAAM,EAAE,GAAG,EAAE,CAAC,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,CAAC,CAAC;QACpD,eAAe,EAAE,eAAe;QAChC,eAAe,EAAE,eAAe;QAChC,eAAe,EAAE,WAAW;QAC5B,aAAa,EAAE,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,EAAE,UAAU,CAAC;KACrD;IACD;QACC,IAAI,EAAE,UAAU;QAChB,EAAE,EAAE,UAAU;QACd,uDAAuD;QACvD,MAAM,EAAE,GAAG,EAAE,CAAC,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,UAAU,CAAC,CAAC;QACrD,eAAe,EAAE,iBAAiB;QAClC,iEAAiE;QACjE,mBAAmB;QACnB,eAAe,EAAE,eAAe;QAChC,eAAe,EAAE,WAAW;QAC5B,aAAa,EAAE,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,UAAU,EAAE,UAAU,EAAE,iBAAiB,CAAC;KACzE;IACD;QACC,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM;QACV,MAAM,EAAE,GAAG,EAAE,CAAC,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,OAAO,CAAC,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC;QAC3E,eAAe,EAAE,cAAc;QAC/B,eAAe,EAAE,aAAa;QAC9B,eAAe,EAAE,WAAW;QAC5B,aAAa,EAAE,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,OAAO,EAAE,UAAU,CAAC;KACnD;IACD;QACC,IAAI,EAAE,YAAY;QAClB,EAAE,EAAE,QAAQ;QACZ,MAAM,EAAE,GAAG,EAAE,CAAC,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,CAAC,CAAC,IAAI,aAAa,CAAC,QAAQ,CAAC;QAC/E,eAAe,EAAE,gBAAgB;QACjC,eAAe,EAAE,eAAe;QAChC,eAAe,EAAE,WAAW;QAC5B,aAAa,EAAE,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,EAAE,UAAU,CAAC;KACrD;IACD;QACC,IAAI,EAAE,mBAAmB;QACzB,EAAE,EAAE,QAAQ;QACZ,+DAA+D;QAC/D,4DAA4D;QAC5D,mBAAmB;QACnB,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,CAAC,CAAC;QAC7E,eAAe,EAAE,wBAAwB;QACzC,eAAe,EAAE,eAAe;QAChC,eAAe,EAAE,WAAW;QAC5B,aAAa,EAAE,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,SAAS,EAAE,UAAU,CAAC;QACrD,iEAAiE;QACjE,YAAY,EAAE,SAAS;KACvB;IACD;QACC,IAAI,EAAE,OAAO;QACb,EAAE,EAAE,OAAO;QACX,6DAA6D;QAC7D,2DAA2D;QAC3D,MAAM,EAAE,GAAG,EAAE,CAAC,IAAA,oBAAU,EAAC,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,QAAQ,CAAC,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC;QAC7E,eAAe,EAAE,gBAAgB;QACjC,eAAe,EAAE,cAAc;QAC/B,eAAe,EAAE,WAAW;QAC5B,aAAa,EAAE,IAAA,gBAAI,EAAC,IAAA,iBAAO,GAAE,EAAE,QAAQ,EAAE,UAAU,CAAC;KACpD;CACD,CAAC;AAEF,wEAAwE;AACxE,SAAgB,gBAAgB,CAAC,EAAU;IAC1C,OAAO,iBAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC3C,CAAC"}
@@ -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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"}