rivet-design 0.11.7 → 0.11.9

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 (46) hide show
  1. package/dist/config/flags.d.ts +3 -0
  2. package/dist/config/flags.d.ts.map +1 -1
  3. package/dist/config/flags.js +1 -0
  4. package/dist/config/flags.js.map +1 -1
  5. package/dist/index.d.ts +1 -8
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +96 -590
  8. package/dist/index.js.map +1 -1
  9. package/dist/install/harnesses.d.ts +82 -0
  10. package/dist/install/harnesses.d.ts.map +1 -0
  11. package/dist/install/harnesses.js +537 -0
  12. package/dist/install/harnesses.js.map +1 -0
  13. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +25 -0
  14. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
  15. package/dist/mcp/agent-variants/WorktreeOrchestrator.js +121 -0
  16. package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
  17. package/dist/mcp/auth/tools.d.ts.map +1 -1
  18. package/dist/mcp/auth/tools.js +63 -12
  19. package/dist/mcp/auth/tools.js.map +1 -1
  20. package/dist/mcp/server.d.ts.map +1 -1
  21. package/dist/mcp/server.js +2 -7
  22. package/dist/mcp/server.js.map +1 -1
  23. package/dist/onboarding/signin.d.ts +31 -0
  24. package/dist/onboarding/signin.d.ts.map +1 -0
  25. package/dist/onboarding/signin.js +235 -0
  26. package/dist/onboarding/signin.js.map +1 -0
  27. package/dist/routes/agentVariants.d.ts.map +1 -1
  28. package/dist/routes/agentVariants.js +528 -72
  29. package/dist/routes/agentVariants.js.map +1 -1
  30. package/dist/services/PrototypeDeployService.d.ts +2 -0
  31. package/dist/services/PrototypeDeployService.d.ts.map +1 -1
  32. package/dist/services/PrototypeDeployService.js +6 -0
  33. package/dist/services/PrototypeDeployService.js.map +1 -1
  34. package/dist/services/TelemetryService.d.ts +70 -0
  35. package/dist/services/TelemetryService.d.ts.map +1 -1
  36. package/dist/services/TelemetryService.js +85 -0
  37. package/dist/services/TelemetryService.js.map +1 -1
  38. package/dist/services/VariantHistoryService.d.ts +18 -0
  39. package/dist/services/VariantHistoryService.d.ts.map +1 -1
  40. package/dist/services/VariantHistoryService.js +27 -3
  41. package/dist/services/VariantHistoryService.js.map +1 -1
  42. package/package.json +2 -1
  43. package/src/ui/dist/assets/main-BtohezP4.js +641 -0
  44. package/src/ui/dist/assets/{main-CtRINpOq.css → main-IY99De7M.css} +1 -1
  45. package/src/ui/dist/index.html +2 -2
  46. package/src/ui/dist/assets/main-DM4pFbJk.js +0 -641
package/dist/index.js CHANGED
@@ -36,13 +36,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.buildCursorLocalRivetEntry = exports.buildCursorHostedRivetEntry = exports.parseArgs = exports.main = exports.registerOwnedDevServerSignalHandlers = exports.registerAgentSignalHandlers = exports.isAgentVariantsEnabled = exports.updateOwnedDevServerHealthOnExit = exports.buildAgentDevServerHealth = exports.resolveAgentDevServer = exports.buildAgentDevServerContext = exports.DEFAULT_USER_PORT = exports.DEFAULT_PORT = void 0;
39
+ exports.parseArgs = exports.main = exports.registerOwnedDevServerSignalHandlers = exports.registerAgentSignalHandlers = exports.isAgentVariantsEnabled = exports.updateOwnedDevServerHealthOnExit = exports.buildAgentDevServerHealth = exports.resolveAgentDevServer = exports.buildAgentDevServerContext = exports.DEFAULT_USER_PORT = exports.DEFAULT_PORT = exports.buildCursorLocalRivetEntry = exports.buildCursorHostedRivetEntry = void 0;
40
40
  /* eslint-disable no-console */
41
- const child_process_1 = require("child_process");
42
41
  const dotenv = __importStar(require("dotenv"));
43
42
  const fs_1 = __importDefault(require("fs"));
44
43
  const open_1 = __importDefault(require("open"));
45
- const os_1 = __importDefault(require("os"));
46
44
  const path_1 = __importDefault(require("path"));
47
45
  const index_core_1 = require("./index-core");
48
46
  const server_1 = require("./server");
@@ -53,7 +51,12 @@ const ConfigManager_1 = require("./services/ConfigManager");
53
51
  const SessionBridgeService_1 = require("./services/SessionBridgeService");
54
52
  const DevServerRuntimeService_1 = require("./services/DevServerRuntimeService");
55
53
  const TelemetryService_1 = require("./services/TelemetryService");
56
- const skillWriter_1 = require("./utils/skillWriter");
54
+ const harnesses_1 = require("./install/harnesses");
55
+ const signin_1 = require("./onboarding/signin");
56
+ // Re-exported for back-compat with existing tests that import them from here.
57
+ var harnesses_2 = require("./install/harnesses");
58
+ Object.defineProperty(exports, "buildCursorHostedRivetEntry", { enumerable: true, get: function () { return harnesses_2.buildCursorHostedRivetEntry; } });
59
+ Object.defineProperty(exports, "buildCursorLocalRivetEntry", { enumerable: true, get: function () { return harnesses_2.buildCursorLocalRivetEntry; } });
57
60
  const portUtils_1 = require("./utils/portUtils");
58
61
  const VariantsRuntime_1 = require("./services/VariantsRuntime");
59
62
  const VisualVariantAgentRunner_1 = require("./services/VisualVariantAgentRunner");
@@ -315,6 +318,13 @@ const main = async (args = process.argv.slice(2)) => {
315
318
  await handleInstall(args[1]);
316
319
  return;
317
320
  }
321
+ if (command === 'signin' || command === 'sign-in') {
322
+ const result = await (0, signin_1.runSignIn)({
323
+ noBrowser: args.includes('--no-browser'),
324
+ nextStepHint: "You're all set — open your agent and say \"Open Rivet\".",
325
+ });
326
+ process.exit(result.ok ? 0 : 1);
327
+ }
318
328
  if (command === 'update') {
319
329
  await handleUpdate();
320
330
  return;
@@ -518,22 +528,32 @@ const main = async (args = process.argv.slice(2)) => {
518
528
  console.log('\n❌ E2E auth required: set RIVET_E2E_EMAIL and RIVET_E2E_PASSWORD (or RIVET_TEST_EMAIL and RIVET_TEST_PASSWORD) in repo secrets. Must be e2etest@gmail.com.');
519
529
  process.exit(1);
520
530
  }
521
- console.log('\n🔐 Authentication required');
522
- console.log('Opening browser for Google login...\n');
523
- const authService = new AuthService_1.AuthService();
524
- const oauthRedirectOrigin = useLocalhostRivetUiRootEntrypoint
525
- ? `http://localhost:${rivetPort}`
526
- : `http://localhost:${rivetPort}/rivet`;
527
- const loginResult = await authService.loginWithGoogle(oauthRedirectOrigin);
528
- if (!loginResult.success) {
529
- console.log(`\n❌ Authentication failed: ${loginResult.error || loginResult.message}`);
530
- process.exit(1);
531
+ if (options.noBrowser) {
532
+ // Headless: can't show the in-browser sign-in page, so fall back to
533
+ // the terminal OAuth flow.
534
+ console.log('\n🔐 Authentication required');
535
+ console.log('Opening browser for Google login...\n');
536
+ const authService = new AuthService_1.AuthService();
537
+ const oauthRedirectOrigin = useLocalhostRivetUiRootEntrypoint
538
+ ? `http://localhost:${rivetPort}`
539
+ : `http://localhost:${rivetPort}/rivet`;
540
+ const loginResult = await authService.loginWithGoogle(oauthRedirectOrigin);
541
+ if (!loginResult.success) {
542
+ console.log(`\n❌ Authentication failed: ${loginResult.error || loginResult.message}`);
543
+ process.exit(1);
544
+ }
545
+ console.log(`\n✅ Authenticated as ${loginResult.user?.email}`);
546
+ if (loginResult.profile) {
547
+ console.log(`💰 Balance: $${loginResult.profile.remaining_spend_usd.toFixed(2)}`);
548
+ }
549
+ didAuthenticate = true;
531
550
  }
532
- console.log(`\n✅ Authenticated as ${loginResult.user?.email}`);
533
- if (loginResult.profile) {
534
- console.log(`💰 Balance: $${loginResult.profile.remaining_spend_usd.toFixed(2)}`);
551
+ else {
552
+ // Interactive: the Rivet UI we open below renders its sign-in screen
553
+ // for unauthenticated users. They sign in there and the editor
554
+ // unlocks live — no need to jump straight to Google from the terminal.
555
+ console.log('\n🔐 Sign in to Rivet in the browser window that opens next.');
535
556
  }
536
- didAuthenticate = true;
537
557
  }
538
558
  }
539
559
  // Open browser to Rivet UI (only if we didn't just authenticate, since OAuth already opened it)
@@ -876,14 +896,12 @@ USAGE:
876
896
  COMMANDS:
877
897
  rivet Start Rivet (auto-login if needed)
878
898
  rivet agent Start Rivet with a terminal AI agent applying visual changes
879
- rivet install claude Set up Rivet for Claude Code
880
- rivet install cursor Set up Rivet for Cursor
881
- rivet install codex Set up Rivet for OpenAI Codex CLI
882
- rivet uninstall claude Remove Rivet from Claude Code
883
- rivet uninstall cursor Remove Rivet from Cursor
884
- rivet uninstall codex Remove Rivet from OpenAI Codex CLI
899
+ rivet install Set up Rivet for every detected agent (Claude Code, Cursor, Codex, …)
900
+ rivet install cursor Set up Rivet for a specific agent only
901
+ rivet uninstall <name> Remove Rivet from one agent (claude|claude-desktop|cursor|codex)
885
902
  rivet uninstall all Remove Rivet from all editors
886
903
  rivet update Update Rivet MCP server for all installed editors
904
+ rivet signin Sign in or create a Rivet account in your browser
887
905
  rivet login Login with Google
888
906
  rivet logout Logout from Rivet
889
907
 
@@ -922,9 +940,8 @@ EXAMPLES:
922
940
  rivet --rivet-port 4000 # Use port 4000 for Rivet (fails if in use)
923
941
  rivet --framework static # Serve static HTML from ./index.html
924
942
  rivet --framework static --entry dist/ # Serve static files from ./dist/ directory
925
- rivet install claude # Configure Rivet as a Claude Code MCP server
926
- rivet install cursor # Configure Rivet for Cursor
927
- rivet install codex # Configure Rivet for OpenAI Codex CLI
943
+ rivet install # Configure Rivet for every detected agent at once
944
+ rivet install cursor # Configure Rivet for Cursor only
928
945
  rivet uninstall cursor # Remove Rivet from Cursor
929
946
  rivet uninstall all # Remove Rivet from all editors
930
947
  rivet login # Manually login with Google
@@ -943,593 +960,82 @@ const showVersion = () => {
943
960
  log.error('Could not read version from package.json', error);
944
961
  }
945
962
  };
946
- const MCP_INSTALL_WELCOME = `
947
- Welcome to Rivet!
948
-
949
- Rivet is a visual editor for your coding agent.
950
- `;
951
- const RIVET_ASCII_LOGO = `
952
-
953
- ****************************************************************************************************
954
- ****************************************************************************************************
955
- *******************************+. :****************************************************************
956
- *******************************: =*************************************:...=*********************
957
- *******************************+=--=*************************************+. =*********************
958
- *****************-....... ..:+*-....**:....=*****:....=**:..........-**+.. ....-*****************
959
- *****************- .... .+*- .***: +***- -*+. :*+. :*****************
960
- *****************- .+****++**- .****. :**+. :**+ :====: .***+ =*********************
961
- *****************- .+********- .****+. -+. :***+ .***+ =*********************
962
- *****************- .+********- .*****+. .: :****+ .++++==--****+ :+++******************
963
- *****************- .+********- .******+. :******:. .+***. =*****************
964
- *****************+++++*********+++++*******+++++++********+++++++++++******+++++++******************
965
- ****************************************************************************************************
966
- ****************************************************************************************************
967
-
968
- `;
969
- /**
970
- * Handle install command — configure Rivet as an MCP server for the specified editor
971
- */
972
- const handleInstall = async (editor) => {
973
- switch (editor) {
974
- case 'claude':
975
- console.log(MCP_INSTALL_WELCOME);
976
- await handleInstallClaude();
977
- break;
978
- case 'claude-desktop':
979
- console.log(MCP_INSTALL_WELCOME);
980
- await handleInstallClaudeDesktop();
981
- break;
982
- case 'cursor':
983
- console.log(MCP_INSTALL_WELCOME);
984
- await handleInstallCursor();
985
- break;
986
- case 'codex':
987
- console.log(MCP_INSTALL_WELCOME);
988
- await handleInstallCodex();
989
- break;
990
- default:
991
- console.log('\nUsage: rivet install <editor>\n');
992
- console.log(' rivet install claude Set up for Claude Code');
993
- console.log(' rivet install claude-desktop Set up for the Claude desktop app');
994
- console.log(' rivet install cursor Set up for Cursor');
995
- console.log(' rivet install codex Set up for OpenAI Codex CLI');
996
- console.log('');
997
- process.exit(editor ? 1 : 0);
998
- }
999
- console.log(RIVET_ASCII_LOGO);
1000
- };
1001
963
  /**
1002
- * Register Rivet with Claude Code MCP. Returns true on success, false on failure.
1003
- * Does NOT call process.exit callers decide how to handle failure.
964
+ * Ask a yes/no question on the terminal. Defaults to yes on a bare Enter.
965
+ * Resolves false immediately if stdin isn't interactive.
1004
966
  */
1005
- const registerClaudeMCP = () => {
1006
- const mcpAddCommand = 'claude mcp add --transport stdio --scope user rivet -- npx -y rivet-design@latest mcp --editor claude';
1007
- try {
1008
- const output = (0, child_process_1.execSync)(mcpAddCommand, { stdio: 'pipe', encoding: 'utf8' });
1009
- process.stdout.write(output);
1010
- console.log('✓ Rivet MCP server added to Claude Code');
1011
- return true;
1012
- }
1013
- catch (err) {
1014
- const stderr = err.stderr ?? '';
1015
- if (stderr.includes('already exists')) {
1016
- console.log('✓ Rivet MCP server already registered');
1017
- return true;
1018
- }
1019
- process.stderr.write(stderr);
1020
- console.log('Run manually:', 'claude mcp add --transport stdio --scope user rivet -- npx -y rivet-design@latest mcp --editor claude');
967
+ const promptYesNo = async (question) => {
968
+ if (!process.stdin.isTTY)
1021
969
  return false;
1022
- }
1023
- };
1024
- const handleInstallClaude = async () => {
1025
- console.log('\nSetting up Rivet for Claude Code...\n');
1026
- try {
1027
- (0, child_process_1.execSync)('claude --version', { stdio: 'ignore' });
1028
- }
1029
- catch {
1030
- console.log('Claude Code is not installed or not in your PATH.');
1031
- console.log('Install it: https://claude.ai/download');
1032
- process.exit(1);
1033
- }
1034
- if (!registerClaudeMCP()) {
1035
- process.exit(1);
1036
- }
1037
- const skillResult = (0, skillWriter_1.writeSkillFileIfNeeded)();
1038
- if (skillResult.success) {
1039
- console.log('✓ Rivet skill installed');
1040
- }
1041
- else {
1042
- console.log(` Warning: could not write skill file to ${path_1.default.join(os_1.default.homedir(), '.claude', 'skills', 'rivet')}: ${skillResult.error}`);
1043
- console.log(' Claude Code will still work with Rivet, but may need manual guidance on tool usage.');
1044
- }
1045
- console.log(`
1046
- Next steps:
1047
- 1. Start a new Claude Code session
1048
- 2. Type /rivet or say "Open Rivet"
1049
- `);
1050
- };
1051
- /**
1052
- * Resolve `npx` to an absolute path using the install-time shell PATH.
1053
- *
1054
- * Why: editors like Claude desktop inherit a sanitized PATH at launch that
1055
- * can put stale node versions (e.g. legacy nvm installs) first, causing
1056
- * `npx` to dispatch to a node so old it can't even `require('node:url')`.
1057
- * Writing the absolute path into the MCP config bypasses that lookup so the
1058
- * editor uses the same `npx` the user has in their interactive shell.
1059
- *
1060
- * Falls back to the literal "npx" with a warning if resolution fails — the
1061
- * prior behavior — so install never hard-blocks on a quirky environment.
1062
- */
1063
- const resolveNpxCommand = () => {
1064
- const which = process.platform === 'win32' ? 'where' : 'which';
970
+ const readline = await Promise.resolve().then(() => __importStar(require('node:readline/promises')));
971
+ const rl = readline.createInterface({
972
+ input: process.stdin,
973
+ output: process.stdout,
974
+ });
1065
975
  try {
1066
- const out = (0, child_process_1.execSync)(`${which} npx`, {
1067
- stdio: ['ignore', 'pipe', 'ignore'],
1068
- encoding: 'utf8',
1069
- });
1070
- // `where` on Windows can return multiple lines (npx, npx.cmd, npx.ps1).
1071
- // Prefer .cmd / .exe over a shebanged shell script so Windows can spawn
1072
- // the resolved path directly.
1073
- const lines = out
1074
- .split(/\r?\n/)
1075
- .map((l) => l.trim())
1076
- .filter((l) => l.length > 0 && fs_1.default.existsSync(l));
1077
- if (lines.length === 0) {
1078
- throw new Error('which returned no usable paths');
1079
- }
1080
- if (process.platform === 'win32') {
1081
- const directlySpawnable = lines.find((l) => /\.(cmd|exe|bat)$/i.test(l));
1082
- return directlySpawnable ?? lines[0];
1083
- }
1084
- return lines[0];
976
+ const answer = (await rl.question(`${question} [Y/n] `)).trim().toLowerCase();
977
+ return answer === '' || answer === 'y' || answer === 'yes';
1085
978
  }
1086
- catch {
1087
- console.log(' Warning: could not resolve `npx` to an absolute path. Falling back to "npx" — if your editor uses an outdated Node, it may fail to start the server.');
1088
- return 'npx';
979
+ finally {
980
+ rl.close();
1089
981
  }
1090
982
  };
1091
- const CURSOR_HOSTED_MCP_URL = 'https://rivet-proxy.onrender.com/mcp';
1092
- const buildCursorHostedRivetEntry = () => ({
1093
- url: CURSOR_HOSTED_MCP_URL,
1094
- });
1095
- exports.buildCursorHostedRivetEntry = buildCursorHostedRivetEntry;
1096
- const buildCursorLocalRivetEntry = () => ({
1097
- command: resolveNpxCommand(),
1098
- args: ['-y', 'rivet-design@latest', 'mcp', '--editor', 'cursor'],
1099
- });
1100
- exports.buildCursorLocalRivetEntry = buildCursorLocalRivetEntry;
1101
983
  /**
1102
- * Surface a diagnostic when an existing rivet MCP entry's `command` is an
1103
- * absolute path that no longer exists. Common when the user removes the
1104
- * node version that was resolved at install time. Update rewrites the entry
1105
- * anyway, but the note explains *why* the entry needed refreshing.
984
+ * After a successful install, nudge the user into Rivet. If they're not signed
985
+ * in yet, offer to open the sign-in page; if they already are, point them at
986
+ * their agent. Stays silent-but-helpful in non-interactive / CI runs.
1106
987
  */
1107
- const warnIfStaleRivetCommand = (entry, editorLabel) => {
1108
- if (typeof entry !== 'object' || entry === null)
1109
- return;
1110
- const cmd = entry.command;
1111
- if (typeof cmd !== 'string')
1112
- return;
1113
- if (!path_1.default.isAbsolute(cmd))
988
+ const promptPostInstall = async () => {
989
+ // Non-interactive / CI: don't probe auth or prompt — just leave guidance.
990
+ if (!process.stdin.isTTY || process.env.CI === 'true') {
991
+ console.log('\nOne more step: run `rivet login` to sign in (or create an account) and start using Rivet.\n');
1114
992
  return;
1115
- if (fs_1.default.existsSync(cmd))
1116
- return;
1117
- console.log(` Note: previous Rivet command for ${editorLabel} (${cmd}) no longer exists. Re-resolving.`);
1118
- };
1119
- const handleInstallCursor = async () => {
1120
- console.log('\nSetting up Rivet for Cursor...\n');
1121
- const cursorDir = path_1.default.join(os_1.default.homedir(), '.cursor');
1122
- if (!fs_1.default.existsSync(cursorDir)) {
1123
- console.log('Cursor does not appear to be installed (~/.cursor not found).');
1124
- console.log('Install it: https://cursor.com');
1125
- process.exit(1);
1126
- }
1127
- const mcpConfigPath = path_1.default.join(cursorDir, 'mcp.json');
1128
- const hostedEntry = (0, exports.buildCursorHostedRivetEntry)();
1129
- const localEntry = (0, exports.buildCursorLocalRivetEntry)();
1130
- let config = {};
1131
- if (fs_1.default.existsSync(mcpConfigPath)) {
1132
- const raw = fs_1.default.readFileSync(mcpConfigPath, 'utf8');
1133
- try {
1134
- const parsed = JSON.parse(raw);
1135
- if (parsed !== null &&
1136
- typeof parsed === 'object' &&
1137
- !Array.isArray(parsed)) {
1138
- config = parsed;
1139
- }
1140
- else {
1141
- console.log(` Error: ${mcpConfigPath} is not a JSON object. Aborting to avoid data loss.`);
1142
- console.log(` Fix or delete the file manually, then re-run.`);
1143
- process.exit(1);
1144
- }
1145
- }
1146
- catch {
1147
- console.log(` Error: could not parse ${mcpConfigPath}. Aborting to avoid data loss.`);
1148
- console.log(` Fix or delete the file manually, then re-run.`);
1149
- process.exit(1);
1150
- }
1151
- }
1152
- // Ensure mcpServers is an object, not some other type
1153
- if (config.mcpServers !== undefined &&
1154
- (typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers))) {
1155
- console.log(` Error: mcpServers in ${mcpConfigPath} is not an object. Aborting to avoid data loss.`);
1156
- process.exit(1);
1157
- }
1158
- config.mcpServers = {
1159
- ...config.mcpServers,
1160
- rivet: hostedEntry,
1161
- 'rivet-local': localEntry,
1162
- };
1163
- fs_1.default.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
1164
- console.log(`✓ Rivet MCP servers added to ${mcpConfigPath}`);
1165
- console.log(`
1166
- Next steps:
1167
- 1. Fully restart Cursor
1168
- 2. Open a new chat and say "Open Rivet"
1169
- `);
1170
- };
1171
- const buildClaudeDesktopRivetEntry = () => ({
1172
- command: resolveNpxCommand(),
1173
- args: ['-y', 'rivet-design@latest', 'mcp', '--editor', 'claude-desktop'],
1174
- });
1175
- /**
1176
- * Path to the Claude desktop app's MCP config file.
1177
- * macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
1178
- * Win: %APPDATA%\Claude\claude_desktop_config.json
1179
- * Linux: ~/.config/Claude/claude_desktop_config.json (unofficial, but the
1180
- * freedesktop default if Anthropic ships a Linux build)
1181
- */
1182
- const claudeDesktopConfigPath = () => {
1183
- const home = os_1.default.homedir();
1184
- if (process.platform === 'darwin') {
1185
- return path_1.default.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
1186
- }
1187
- if (process.platform === 'win32') {
1188
- const appData = process.env.APPDATA ?? path_1.default.join(home, 'AppData', 'Roaming');
1189
- return path_1.default.join(appData, 'Claude', 'claude_desktop_config.json');
1190
- }
1191
- return path_1.default.join(home, '.config', 'Claude', 'claude_desktop_config.json');
1192
- };
1193
- const handleInstallClaudeDesktop = async () => {
1194
- console.log('\nSetting up Rivet for the Claude desktop app...\n');
1195
- const mcpConfigPath = claudeDesktopConfigPath();
1196
- const claudeDir = path_1.default.dirname(mcpConfigPath);
1197
- if (!fs_1.default.existsSync(claudeDir)) {
1198
- console.log(`Claude desktop does not appear to be installed (${claudeDir} not found).`);
1199
- console.log('Install it: https://claude.ai/download');
1200
- process.exit(1);
1201
- }
1202
- const rivetEntry = buildClaudeDesktopRivetEntry();
1203
- let config = {};
1204
- if (fs_1.default.existsSync(mcpConfigPath)) {
1205
- const raw = fs_1.default.readFileSync(mcpConfigPath, 'utf8');
1206
- try {
1207
- const parsed = JSON.parse(raw);
1208
- if (parsed !== null &&
1209
- typeof parsed === 'object' &&
1210
- !Array.isArray(parsed)) {
1211
- config = parsed;
1212
- }
1213
- else {
1214
- console.log(` Error: ${mcpConfigPath} is not a JSON object. Aborting to avoid data loss.`);
1215
- console.log(` Fix or delete the file manually, then re-run.`);
1216
- process.exit(1);
1217
- }
1218
- }
1219
- catch {
1220
- console.log(` Error: could not parse ${mcpConfigPath}. Aborting to avoid data loss.`);
1221
- console.log(` Fix or delete the file manually, then re-run.`);
1222
- process.exit(1);
1223
- }
1224
- }
1225
- if (config.mcpServers !== undefined &&
1226
- (typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers))) {
1227
- console.log(` Error: mcpServers in ${mcpConfigPath} is not an object. Aborting to avoid data loss.`);
1228
- process.exit(1);
1229
- }
1230
- config.mcpServers = { ...config.mcpServers, rivet: rivetEntry };
1231
- fs_1.default.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
1232
- console.log(`✓ Rivet MCP server added to ${mcpConfigPath}`);
1233
- console.log(`
1234
- Next steps:
1235
- 1. Fully quit and reopen the Claude desktop app
1236
- 2. Open a new chat and say "Open Rivet"
1237
- `);
1238
- };
1239
- /**
1240
- * Register Rivet with Codex MCP. Returns true on success, false on failure.
1241
- * Does NOT call process.exit — callers decide how to handle failure.
1242
- */
1243
- const registerCodexMCP = () => {
1244
- const mcpAddCommand = 'codex mcp add rivet -- npx -y rivet-design@latest mcp --editor codex';
1245
- try {
1246
- (0, child_process_1.execSync)(mcpAddCommand, { stdio: 'inherit' });
1247
- console.log('✓ Rivet MCP server added to Codex');
1248
- return true;
1249
- }
1250
- catch {
1251
- console.log('Run manually:', 'codex mcp add rivet -- npx -y rivet-design@latest mcp --editor codex');
1252
- return false;
1253
- }
1254
- };
1255
- const handleInstallCodex = async () => {
1256
- console.log('\nSetting up Rivet for OpenAI Codex CLI...\n');
1257
- // Check codex binary
1258
- try {
1259
- (0, child_process_1.execSync)('codex --version', { stdio: 'ignore' });
1260
- }
1261
- catch {
1262
- console.log('Codex CLI is not installed or not in your PATH.');
1263
- console.log('Install it: https://github.com/openai/codex');
1264
- process.exit(1);
1265
- }
1266
- if (!registerCodexMCP()) {
1267
- process.exit(1);
1268
993
  }
1269
- console.log(`
1270
- Next steps:
1271
- 1. Start a new Codex session
1272
- 2. Say "Open Rivet"
1273
- `);
1274
- };
1275
- const handleUninstallClaude = () => {
1276
- try {
1277
- (0, child_process_1.execSync)('claude --version', { stdio: 'ignore' });
1278
- }
1279
- catch {
1280
- console.log('Claude Code is not installed or not in your PATH — skipping.');
1281
- return false;
1282
- }
1283
- let madeChanges = false;
1284
- try {
1285
- (0, child_process_1.execSync)('claude mcp remove rivet', { stdio: 'pipe' });
1286
- console.log('✓ Rivet MCP server removed from Claude Code');
1287
- madeChanges = true;
1288
- }
1289
- catch {
1290
- console.log(' Rivet was not registered with Claude Code (nothing to remove).');
1291
- }
1292
- // Remove skill file
1293
- const skillDir = path_1.default.join(os_1.default.homedir(), '.claude', 'skills', 'rivet');
1294
- if (fs_1.default.existsSync(skillDir)) {
1295
- fs_1.default.rmSync(skillDir, { recursive: true, force: true });
1296
- console.log('✓ Rivet skill file removed');
1297
- madeChanges = true;
1298
- }
1299
- return madeChanges;
1300
- };
1301
- const handleUninstallCursor = () => {
1302
- const mcpConfigPath = path_1.default.join(os_1.default.homedir(), '.cursor', 'mcp.json');
1303
- if (!fs_1.default.existsSync(mcpConfigPath)) {
1304
- console.log(' No Cursor MCP config found — nothing to remove.');
1305
- return false;
1306
- }
1307
- try {
1308
- const raw = fs_1.default.readFileSync(mcpConfigPath, 'utf8');
1309
- const config = JSON.parse(raw);
1310
- if (!config.mcpServers ||
1311
- (!('rivet' in config.mcpServers) && !('rivet-local' in config.mcpServers))) {
1312
- console.log(' Rivet was not registered with Cursor (nothing to remove).');
1313
- return false;
1314
- }
1315
- delete config.mcpServers['rivet'];
1316
- delete config.mcpServers['rivet-local'];
1317
- fs_1.default.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
1318
- console.log(`✓ Rivet MCP server removed from ${mcpConfigPath}`);
1319
- return true;
1320
- }
1321
- catch {
1322
- console.log(` Error: could not parse ${mcpConfigPath}. Fix or delete the file manually.`);
1323
- return false;
1324
- }
1325
- };
1326
- const handleUninstallClaudeDesktop = () => {
1327
- const mcpConfigPath = claudeDesktopConfigPath();
1328
- if (!fs_1.default.existsSync(mcpConfigPath)) {
1329
- console.log(' No Claude desktop MCP config found — nothing to remove.');
1330
- return false;
994
+ // Validate the token (refreshing if stale) rather than trusting its presence,
995
+ // so a revoked/expired token still gets a fresh sign-in prompt.
996
+ if (await new AuthService_1.AuthService().ensureAuthenticated()) {
997
+ const email = (0, ConfigManager_1.getConfigManager)().getEmail();
998
+ console.log(`\nYou're signed in${email ? ` as ${email}` : ''}. Open your agent and say "Open Rivet" to start.\n`);
999
+ return;
1331
1000
  }
1332
- try {
1333
- const raw = fs_1.default.readFileSync(mcpConfigPath, 'utf8');
1334
- const config = JSON.parse(raw);
1335
- if (!config.mcpServers || !('rivet' in config.mcpServers)) {
1336
- console.log(' Rivet was not registered with the Claude desktop app (nothing to remove).');
1337
- return false;
1338
- }
1339
- delete config.mcpServers['rivet'];
1340
- fs_1.default.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
1341
- console.log(`✓ Rivet MCP server removed from ${mcpConfigPath}`);
1342
- return true;
1001
+ const wantsSignIn = await promptYesNo('Sign in or create your Rivet account now?');
1002
+ if (wantsSignIn) {
1003
+ await (0, signin_1.runSignIn)({
1004
+ nextStepHint: "You're all set open your agent and say \"Open Rivet\".",
1005
+ });
1343
1006
  }
1344
- catch {
1345
- console.log(` Error: could not parse ${mcpConfigPath}. Fix or delete the file manually.`);
1346
- return false;
1007
+ else {
1008
+ console.log("\nNo problem run `rivet login` whenever you're ready.\n");
1347
1009
  }
1348
1010
  };
1349
- const handleUninstallCodex = () => {
1350
- try {
1351
- (0, child_process_1.execSync)('codex --version', { stdio: 'ignore' });
1352
- }
1353
- catch {
1354
- console.log('Codex CLI is not installed or not in your PATH — skipping.');
1355
- return false;
1356
- }
1357
- try {
1358
- (0, child_process_1.execSync)('codex mcp remove rivet', { stdio: 'pipe' });
1359
- console.log('✓ Rivet MCP server removed from Codex');
1360
- return true;
1361
- }
1362
- catch {
1363
- console.log(' Rivet was not registered with Codex (nothing to remove).');
1364
- return false;
1365
- }
1011
+ const handleInstall = async (editor) => {
1012
+ const { exitCode } = (0, harnesses_1.installHarnesses)(editor);
1013
+ if (exitCode !== 0)
1014
+ process.exit(exitCode);
1015
+ await promptPostInstall();
1366
1016
  };
1367
- /**
1368
- * Handle uninstall command — remove Rivet MCP server for the specified editor (or all)
1369
- */
1370
1017
  const handleUninstall = async (editor) => {
1371
- switch (editor) {
1372
- case 'claude': {
1373
- console.log('\nUninstalling Rivet from Claude Code...\n');
1374
- const changed = handleUninstallClaude();
1375
- if (changed)
1376
- console.log('\nRestart Claude Code to apply changes.\n');
1377
- break;
1378
- }
1379
- case 'claude-desktop': {
1380
- console.log('\nUninstalling Rivet from the Claude desktop app...\n');
1381
- const changed = handleUninstallClaudeDesktop();
1382
- if (changed)
1383
- console.log('\nFully restart the Claude desktop app to apply changes.\n');
1384
- break;
1385
- }
1386
- case 'cursor': {
1387
- console.log('\nUninstalling Rivet from Cursor...\n');
1388
- const changed = handleUninstallCursor();
1389
- if (changed)
1390
- console.log('\nRestart Cursor to apply changes.\n');
1391
- break;
1392
- }
1393
- case 'codex': {
1394
- console.log('\nUninstalling Rivet from OpenAI Codex CLI...\n');
1395
- const changed = handleUninstallCodex();
1396
- if (changed)
1397
- console.log('\nStart a new Codex session to apply changes.\n');
1398
- break;
1399
- }
1400
- case 'all': {
1401
- console.log('\nUninstalling Rivet from all editors...\n');
1402
- const claudeChanged = handleUninstallClaude();
1403
- const claudeDesktopChanged = handleUninstallClaudeDesktop();
1404
- const cursorChanged = handleUninstallCursor();
1405
- const codexChanged = handleUninstallCodex();
1406
- if (claudeChanged ||
1407
- claudeDesktopChanged ||
1408
- cursorChanged ||
1409
- codexChanged) {
1410
- console.log('\nRestart your editor sessions to apply changes.\n');
1411
- }
1412
- break;
1413
- }
1414
- default:
1415
- console.log('\nUsage: rivet uninstall <editor>\n');
1416
- console.log(' rivet uninstall claude Remove from Claude Code');
1417
- console.log(' rivet uninstall claude-desktop Remove from the Claude desktop app');
1418
- console.log(' rivet uninstall cursor Remove from Cursor');
1419
- console.log(' rivet uninstall codex Remove from OpenAI Codex CLI');
1420
- console.log(' rivet uninstall all Remove from all editors');
1421
- console.log('');
1422
- process.exit(editor ? 1 : 0);
1423
- }
1018
+ const { exitCode } = (0, harnesses_1.uninstallHarnesses)(editor);
1019
+ if (exitCode !== 0)
1020
+ process.exit(exitCode);
1424
1021
  };
1425
- /**
1426
- * Handle update command — re-register Rivet MCP server for all detected editors
1427
- */
1428
1022
  const handleUpdate = async () => {
1429
- console.log('\nUpdating Rivet...\n');
1430
- let updatedAny = false;
1431
- // Claude Code — only run if installed; surface re-add failure so we don't leave user without MCP
1432
- let claudeInstalled = false;
1433
- try {
1434
- (0, child_process_1.execSync)('claude --version', { stdio: 'ignore' });
1435
- claudeInstalled = true;
1436
- }
1437
- catch {
1438
- // Claude not installed — skip
1439
- }
1440
- if (claudeInstalled) {
1441
- try {
1442
- (0, child_process_1.execSync)('claude mcp remove rivet', { stdio: 'pipe' });
1443
- }
1444
- catch {
1445
- // not registered, that's fine
1446
- }
1447
- if (registerClaudeMCP()) {
1448
- console.log('✓ Updated Rivet MCP server for Claude Code');
1449
- const skillResult = (0, skillWriter_1.writeSkillFileIfNeeded)();
1450
- if (skillResult.success) {
1451
- console.log('✓ Updated Rivet skill for Claude Code');
1452
- }
1453
- else {
1454
- console.log(` Warning: could not write skill file (${skillResult.error})`);
1455
- }
1456
- updatedAny = true;
1457
- }
1458
- else {
1459
- console.log(' Failed to re-add Rivet for Claude Code. Run: rivet install claude');
1460
- }
1461
- }
1462
- // Cursor — only update if rivet was already registered
1463
- const cursorMcpPath = path_1.default.join(os_1.default.homedir(), '.cursor', 'mcp.json');
1464
- if (fs_1.default.existsSync(cursorMcpPath)) {
1465
- try {
1466
- const raw = fs_1.default.readFileSync(cursorMcpPath, 'utf8');
1467
- const config = JSON.parse(raw);
1468
- if (config.mcpServers &&
1469
- ('rivet' in config.mcpServers || 'rivet-local' in config.mcpServers)) {
1470
- warnIfStaleRivetCommand(config.mcpServers['rivet'], 'Cursor');
1471
- warnIfStaleRivetCommand(config.mcpServers['rivet-local'], 'Cursor');
1472
- config.mcpServers['rivet'] = (0, exports.buildCursorHostedRivetEntry)();
1473
- config.mcpServers['rivet-local'] = (0, exports.buildCursorLocalRivetEntry)();
1474
- fs_1.default.writeFileSync(cursorMcpPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
1475
- console.log('✓ Updated Rivet MCP servers for Cursor');
1476
- updatedAny = true;
1477
- }
1478
- }
1479
- catch {
1480
- console.log(' Warning: could not update Cursor MCP config');
1481
- }
1482
- }
1483
- // Claude desktop — only update if rivet was already registered
1484
- const claudeDesktopMcpPath = claudeDesktopConfigPath();
1485
- if (fs_1.default.existsSync(claudeDesktopMcpPath)) {
1486
- try {
1487
- const raw = fs_1.default.readFileSync(claudeDesktopMcpPath, 'utf8');
1488
- const config = JSON.parse(raw);
1489
- if (config.mcpServers && 'rivet' in config.mcpServers) {
1490
- warnIfStaleRivetCommand(config.mcpServers['rivet'], 'the Claude desktop app');
1491
- config.mcpServers['rivet'] = buildClaudeDesktopRivetEntry();
1492
- fs_1.default.writeFileSync(claudeDesktopMcpPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
1493
- console.log('✓ Updated Rivet MCP server for the Claude desktop app');
1494
- updatedAny = true;
1495
- }
1496
- }
1497
- catch {
1498
- console.log(' Warning: could not update Claude desktop MCP config');
1499
- }
1500
- }
1501
- // Codex — remove then re-add so update works when already registered
1502
- try {
1503
- (0, child_process_1.execSync)('codex --version', { stdio: 'ignore' });
1504
- try {
1505
- (0, child_process_1.execSync)('codex mcp remove rivet', { stdio: 'pipe' });
1506
- }
1507
- catch {
1508
- // not registered, that's fine
1509
- }
1510
- if (registerCodexMCP()) {
1511
- console.log('✓ Updated Rivet MCP server for Codex');
1512
- updatedAny = true;
1513
- }
1514
- }
1515
- catch {
1516
- // Codex not installed — skip
1517
- }
1518
- if (!updatedAny) {
1519
- console.log("No editors found to update. Run 'rivet install <editor>' first.");
1520
- console.log(' rivet install claude');
1521
- console.log(' rivet install claude-desktop');
1522
- console.log(' rivet install cursor');
1523
- console.log(' rivet install codex');
1524
- }
1525
- else {
1526
- console.log('\nRestart your editor session to use the latest Rivet.\n');
1527
- }
1023
+ (0, harnesses_1.updateHarnesses)();
1528
1024
  };
1529
1025
  /**
1530
1026
  * Handle login command
1531
1027
  */
1532
1028
  const handleLogin = async () => {
1029
+ // Interactive terminal: open Rivet's own sign-in page rather than shelling
1030
+ // straight to Google. The page's "Sign in with Google" button kicks off the
1031
+ // OAuth flow and stores the token locally.
1032
+ if (process.stdin.isTTY) {
1033
+ const result = await (0, signin_1.runSignIn)({ nextStepHint: 'You can now run: rivet' });
1034
+ if (!result.ok)
1035
+ process.exit(1);
1036
+ return;
1037
+ }
1038
+ // Headless / non-interactive: fall back to the terminal OAuth flow.
1533
1039
  console.log('\n🔐 Rivet Login with Google\n');
1534
1040
  const authService = new AuthService_1.AuthService();
1535
1041
  console.log('Opening browser for Google authentication...');