rentline-sandbox 0.1.2
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/LICENSE +34 -0
- package/README.md +287 -0
- package/SKILL.md +210 -0
- package/dist/admin-YB2G7ZYZ.js +54 -0
- package/dist/auth-M7RU2YB3.js +119 -0
- package/dist/chunk-Q5AKRRZ2.js +59 -0
- package/dist/chunk-X3OZHOPM.js +90 -0
- package/dist/game-KCQW5KOZ.js +133 -0
- package/dist/index.js +39 -0
- package/dist/mortgage-7UCDSSUV.js +92 -0
- package/dist/server-NRWOBJJN.js +762 -0
- package/dist/server.js +844 -0
- package/dist/setup-JPLRGUPW.js +215 -0
- package/dist/trade-5BAZ6QFQ.js +67 -0
- package/llms.txt +78 -0
- package/package.json +65 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_API_URL,
|
|
4
|
+
loadConfig,
|
|
5
|
+
saveConfig
|
|
6
|
+
} from "./chunk-Q5AKRRZ2.js";
|
|
7
|
+
import {
|
|
8
|
+
createClient
|
|
9
|
+
} from "./chunk-X3OZHOPM.js";
|
|
10
|
+
|
|
11
|
+
// src/setup.ts
|
|
12
|
+
import { createInterface } from "readline";
|
|
13
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync } from "fs";
|
|
14
|
+
import { homedir, platform } from "os";
|
|
15
|
+
import { join, dirname } from "path";
|
|
16
|
+
import { fileURLToPath } from "url";
|
|
17
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
var __dirname = dirname(__filename);
|
|
19
|
+
function parseSetupArgs(args) {
|
|
20
|
+
const opts = {};
|
|
21
|
+
for (let i = 0; i < args.length; i++) {
|
|
22
|
+
const a = args[i];
|
|
23
|
+
if (a === "--key" || a === "-k") opts.key = args[++i];
|
|
24
|
+
else if (a === "--url" || a === "-u") opts.url = args[++i];
|
|
25
|
+
else if (a === "--name") opts.name = args[++i];
|
|
26
|
+
else if (a === "--client" || a === "-c") opts.client = args[++i];
|
|
27
|
+
else if (a === "--scope") opts.scope = args[++i];
|
|
28
|
+
else if (a === "--yes" || a === "-y") opts.yes = true;
|
|
29
|
+
}
|
|
30
|
+
return opts;
|
|
31
|
+
}
|
|
32
|
+
async function runSetup(opts) {
|
|
33
|
+
console.log("\nRentline Sandbox \u2014 MCP Setup\n");
|
|
34
|
+
const rl = opts.yes ? null : createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
const ask = (q, fallback = "") => {
|
|
36
|
+
if (opts.yes || !rl) return Promise.resolve(fallback);
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
rl.question(q, (ans) => resolve(ans.trim() || fallback));
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
let apiKey = opts.key;
|
|
42
|
+
if (!apiKey) {
|
|
43
|
+
const existing2 = loadConfig();
|
|
44
|
+
if (existing2?.api_key && opts.yes) {
|
|
45
|
+
apiKey = existing2.api_key;
|
|
46
|
+
} else {
|
|
47
|
+
apiKey = await ask(
|
|
48
|
+
`Enter your sandbox API key${existing2?.api_key ? " (press Enter to keep existing)" : ""}: `,
|
|
49
|
+
existing2?.api_key ?? ""
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!apiKey) {
|
|
54
|
+
console.error("API key is required. Get one from your sandbox-api admin.");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const existing = loadConfig();
|
|
58
|
+
const apiUrl = opts.url ?? await ask(
|
|
59
|
+
`Sandbox API URL [${existing?.api_url ?? DEFAULT_API_URL}]: `,
|
|
60
|
+
existing?.api_url ?? DEFAULT_API_URL
|
|
61
|
+
);
|
|
62
|
+
const displayName = opts.name ?? await ask(
|
|
63
|
+
`Your default display name [${existing?.display_name ?? "Player"}]: `,
|
|
64
|
+
existing?.display_name ?? "Player"
|
|
65
|
+
);
|
|
66
|
+
process.stdout.write("Verifying connectivity\u2026 ");
|
|
67
|
+
try {
|
|
68
|
+
const c = createClient({ apiUrl, apiKey });
|
|
69
|
+
const h = await c.health();
|
|
70
|
+
console.log(`OK (${h.service})`);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.log("FAILED");
|
|
73
|
+
console.error(`Cannot reach ${apiUrl}: ${e}`);
|
|
74
|
+
if (!opts.yes) {
|
|
75
|
+
const cont = await ask("Save config anyway? [y/N]: ", "n");
|
|
76
|
+
if (cont.toLowerCase() !== "y") {
|
|
77
|
+
rl?.close();
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
saveConfig({ api_key: apiKey, api_url: apiUrl, display_name: displayName, created_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
83
|
+
console.log("Credentials saved.\n");
|
|
84
|
+
let clientName = opts.client ?? detectClient();
|
|
85
|
+
if (!clientName) {
|
|
86
|
+
console.log("Which MCP client do you use?");
|
|
87
|
+
const clients = ["claude-code", "claude-desktop", "cursor", "windsurf", "opencode", "zed", "cline", "other"];
|
|
88
|
+
clients.forEach((c, i) => console.log(` ${i + 1}. ${c}`));
|
|
89
|
+
const choice = await ask("Enter number or name: ", "other");
|
|
90
|
+
const idx = parseInt(choice);
|
|
91
|
+
clientName = isNaN(idx) ? choice : clients[idx - 1] ?? "other";
|
|
92
|
+
}
|
|
93
|
+
await installForClient(clientName, opts.scope ?? "user", apiKey, apiUrl, displayName);
|
|
94
|
+
rl?.close();
|
|
95
|
+
console.log("\nSetup complete. Restart your AI client to load the Rentline Sandbox MCP server.\n");
|
|
96
|
+
}
|
|
97
|
+
function detectClient() {
|
|
98
|
+
const env = process.env;
|
|
99
|
+
if (env.CLAUDE_CODE || env.ANTHROPIC_CLAUDE_CODE) return "claude-code";
|
|
100
|
+
if (env.CURSOR_TRACE_ID || env.CURSOR_SESSION_ID) return "cursor";
|
|
101
|
+
if (env.WINDSURF_SESSION) return "windsurf";
|
|
102
|
+
if (env.OPENCODE_PROJECT || env.OPENCODE_SESSION) return "opencode";
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
var MCP_SERVER_ENTRY = {
|
|
106
|
+
command: "npx",
|
|
107
|
+
args: ["-y", "rentline-sandbox"],
|
|
108
|
+
env: {}
|
|
109
|
+
};
|
|
110
|
+
function mcpEntry(apiKey, apiUrl) {
|
|
111
|
+
return {
|
|
112
|
+
...MCP_SERVER_ENTRY,
|
|
113
|
+
env: {
|
|
114
|
+
SANDBOX_API_KEY: apiKey,
|
|
115
|
+
SANDBOX_API_URL: apiUrl
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function installForClient(clientName, scope, apiKey, apiUrl, displayName) {
|
|
120
|
+
const entry = mcpEntry(apiKey, apiUrl);
|
|
121
|
+
switch (clientName) {
|
|
122
|
+
case "claude-code": {
|
|
123
|
+
const { execSync } = await import("child_process");
|
|
124
|
+
const envFlags = Object.entries(entry.env ?? {}).map(([k, v]) => `-e ${k}=${v}`).join(" ");
|
|
125
|
+
const cmd = `claude mcp add rentline-sandbox --scope ${scope} ${envFlags} -- npx -y rentline-sandbox`;
|
|
126
|
+
try {
|
|
127
|
+
execSync(cmd, { stdio: "pipe" });
|
|
128
|
+
console.log(`Installed via claude CLI (scope=${scope})`);
|
|
129
|
+
} catch {
|
|
130
|
+
const file = join(homedir(), ".claude.json");
|
|
131
|
+
patchMcpJson(file, "rentline-sandbox", entry, "mcpServers");
|
|
132
|
+
console.log(`Patched ${file}`);
|
|
133
|
+
}
|
|
134
|
+
const skillSrc = join(__dirname, "../SKILL.md");
|
|
135
|
+
if (existsSync(skillSrc)) {
|
|
136
|
+
const targets = [
|
|
137
|
+
join(homedir(), ".claude", "skills", "rentline-sandbox"),
|
|
138
|
+
join(homedir(), ".agents", "skills", "rentline-sandbox")
|
|
139
|
+
];
|
|
140
|
+
for (const dir of targets) {
|
|
141
|
+
mkdirSync(dir, { recursive: true });
|
|
142
|
+
copyFileSync(skillSrc, join(dir, "SKILL.md"));
|
|
143
|
+
console.log(`SKILL.md \u2192 ${dir}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "claude-desktop": {
|
|
149
|
+
const file = platform() === "win32" ? join(process.env.APPDATA ?? homedir(), "Claude", "claude_desktop_config.json") : join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
150
|
+
patchMcpJson(file, "rentline-sandbox", entry, "mcpServers");
|
|
151
|
+
console.log(`Patched ${file}`);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case "cursor": {
|
|
155
|
+
const file = scope === "project" ? join(process.cwd(), ".cursor", "mcp.json") : join(homedir(), ".cursor", "mcp.json");
|
|
156
|
+
patchMcpJson(file, "rentline-sandbox", entry, "mcpServers");
|
|
157
|
+
console.log(`Patched ${file}`);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "windsurf": {
|
|
161
|
+
const file = join(process.cwd(), ".windsurf", "mcp.json");
|
|
162
|
+
patchMcpJson(file, "rentline-sandbox", entry, "mcpServers");
|
|
163
|
+
console.log(`Patched ${file}`);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "opencode": {
|
|
167
|
+
const file = scope === "project" ? join(process.cwd(), "opencode.json") : join(homedir(), ".config", "opencode", "config.json");
|
|
168
|
+
patchMcpJson(file, "rentline-sandbox", entry, "mcp");
|
|
169
|
+
console.log(`Patched ${file}`);
|
|
170
|
+
const skillSrc = join(__dirname, "../SKILL.md");
|
|
171
|
+
if (existsSync(skillSrc)) {
|
|
172
|
+
const targets = [
|
|
173
|
+
join(homedir(), ".config", "opencode", "skills", "rentline-sandbox"),
|
|
174
|
+
join(homedir(), ".claude", "skills", "rentline-sandbox"),
|
|
175
|
+
join(homedir(), ".agents", "skills", "rentline-sandbox")
|
|
176
|
+
];
|
|
177
|
+
for (const dir of targets) {
|
|
178
|
+
mkdirSync(dir, { recursive: true });
|
|
179
|
+
copyFileSync(skillSrc, join(dir, "SKILL.md"));
|
|
180
|
+
console.log(`SKILL.md \u2192 ${dir}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case "zed":
|
|
186
|
+
case "cline":
|
|
187
|
+
case "warp":
|
|
188
|
+
case "other":
|
|
189
|
+
default: {
|
|
190
|
+
console.log(`
|
|
191
|
+
Add the following to your MCP client config:
|
|
192
|
+
`);
|
|
193
|
+
console.log(JSON.stringify({ "rentline-sandbox": entry }, null, 2));
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function patchMcpJson(filePath, serverName, entry, key) {
|
|
199
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
200
|
+
let config = {};
|
|
201
|
+
if (existsSync(filePath)) {
|
|
202
|
+
try {
|
|
203
|
+
config = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
204
|
+
} catch {
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const servers = config[key] ?? {};
|
|
208
|
+
servers[serverName] = entry;
|
|
209
|
+
config[key] = servers;
|
|
210
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
parseSetupArgs,
|
|
214
|
+
runSetup
|
|
215
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getApiKey,
|
|
4
|
+
getApiUrl,
|
|
5
|
+
requireConfig
|
|
6
|
+
} from "./chunk-Q5AKRRZ2.js";
|
|
7
|
+
import {
|
|
8
|
+
createClient
|
|
9
|
+
} from "./chunk-X3OZHOPM.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/trade.ts
|
|
12
|
+
function client(cmd) {
|
|
13
|
+
const opts = cmd.optsWithGlobals();
|
|
14
|
+
const cfg = requireConfig();
|
|
15
|
+
return createClient({ apiUrl: getApiUrl(opts.url), apiKey: getApiKey(opts.apiKey) ?? cfg.api_key });
|
|
16
|
+
}
|
|
17
|
+
function registerTrade(program) {
|
|
18
|
+
const trade = program.command("trade").description("Buy and sell property tokens");
|
|
19
|
+
trade.command("buy <game-id>").description("Buy fractional property tokens (cash purchase, no mortgage)").requiredOption("--property <id>", "Property ID from the game pool").requiredOption("--tokens <n>", "Number of tokens to buy (fractions allowed)").action(async (gameId, opts, cmd) => {
|
|
20
|
+
const res = await client(cmd).trade(gameId, {
|
|
21
|
+
property_id: opts.property,
|
|
22
|
+
direction: "buy",
|
|
23
|
+
tokens: parseFloat(opts.tokens)
|
|
24
|
+
});
|
|
25
|
+
console.log(`BUY ${res.tokens} tokens @ $${res.price_per_token_usd?.toFixed(2)}`);
|
|
26
|
+
console.log(`Cost: $${res.amount_usdc?.toFixed(2)}`);
|
|
27
|
+
console.log(`Transaction: ${res.transaction_id}`);
|
|
28
|
+
});
|
|
29
|
+
trade.command("sell <game-id>").description("Sell fractional property tokens back to the pool").requiredOption("--property <id>", "Property ID").requiredOption("--tokens <n>", "Number of tokens to sell").action(async (gameId, opts, cmd) => {
|
|
30
|
+
const res = await client(cmd).trade(gameId, {
|
|
31
|
+
property_id: opts.property,
|
|
32
|
+
direction: "sell",
|
|
33
|
+
tokens: parseFloat(opts.tokens)
|
|
34
|
+
});
|
|
35
|
+
console.log(`SELL ${res.tokens} tokens @ $${res.price_per_token_usd?.toFixed(2)}`);
|
|
36
|
+
console.log(`Proceeds: $${res.amount_usdc?.toFixed(2)}`);
|
|
37
|
+
console.log(`Transaction: ${res.transaction_id}`);
|
|
38
|
+
});
|
|
39
|
+
program.command("portfolio <game-id> <player-id>").description("Show holdings, P&L, NAV, and leverage for a player").action(async (gameId, playerId, cmd) => {
|
|
40
|
+
const p = await client(cmd).getPortfolio(gameId, playerId);
|
|
41
|
+
console.log(`
|
|
42
|
+
Portfolio: ${p.display_name}`);
|
|
43
|
+
console.log(`${"\u2500".repeat(70)}`);
|
|
44
|
+
console.log(`Cash balance: $${p.usdc_balance.toLocaleString("en-US", { maximumFractionDigits: 2 })}`);
|
|
45
|
+
console.log(`Total debt: -$${p.total_debt.toLocaleString("en-US", { maximumFractionDigits: 2 })}`);
|
|
46
|
+
console.log(`Gross assets: $${p.gross_asset_value.toLocaleString("en-US", { maximumFractionDigits: 2 })}`);
|
|
47
|
+
console.log(`NAV: $${p.nav.toLocaleString("en-US", { maximumFractionDigits: 2 })}`);
|
|
48
|
+
console.log(`Leverage ratio: ${(p.leverage_ratio * 100).toFixed(1)}%`);
|
|
49
|
+
if (p.holdings.length) {
|
|
50
|
+
console.log(`
|
|
51
|
+
Holdings:`);
|
|
52
|
+
console.log(`${"Property".padEnd(30)}${"Tokens".padStart(10)}${"Value".padStart(14)}${"P&L".padStart(12)}${"Yield".padStart(10)}`);
|
|
53
|
+
console.log("\u2500".repeat(76));
|
|
54
|
+
for (const h of p.holdings) {
|
|
55
|
+
const pnlSign = h.unrealized_pnl_usd >= 0 ? "+" : "";
|
|
56
|
+
console.log(
|
|
57
|
+
`${(h.property_name ?? h.property_id).slice(0, 28).padEnd(30)}${h.tokens_held.toFixed(2).padStart(10)}$${h.current_value_usd.toFixed(2).padStart(13)}${pnlSign}$${h.unrealized_pnl_usd.toFixed(2).padStart(11)}$${h.total_rent_received_usd.toFixed(2).padStart(9)}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
console.log("\nNo holdings yet.");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
registerTrade
|
|
67
|
+
};
|
package/llms.txt
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Rentline Sandbox
|
|
2
|
+
|
|
3
|
+
Turn-based real estate investment simulation. Players compete over tokenised properties using
|
|
4
|
+
simulated tUSDC with mortgages, Fed rate cycles, macro events, property condition grades,
|
|
5
|
+
PACE liens, and investor tiers.
|
|
6
|
+
|
|
7
|
+
## MCP server
|
|
8
|
+
|
|
9
|
+
Command: sandbox-mcp
|
|
10
|
+
Transport: stdio JSON-RPC (Model Context Protocol)
|
|
11
|
+
|
|
12
|
+
Required env vars:
|
|
13
|
+
- SANDBOX_API_URL — API base URL (e.g. https://sandbox-api.rentline.xyz)
|
|
14
|
+
- SANDBOX_API_KEY — sb_ user key or admin key
|
|
15
|
+
|
|
16
|
+
## 35 tools
|
|
17
|
+
|
|
18
|
+
### Game management
|
|
19
|
+
- list_games — list open game rooms (lobby/trading)
|
|
20
|
+
- get_game — full game state: players, properties, turn, Fed rate, settings
|
|
21
|
+
- create_game — create a game with full mortgage/Fed/grade config + optional bots
|
|
22
|
+
- create_game_from_preset — one-call presets: quick, standard, leveraged, distressed, long_run
|
|
23
|
+
- join_game — join via invite code
|
|
24
|
+
- mark_ready — toggle ready state; game advances when all humans ready
|
|
25
|
+
- advance_turn — host: run all 7 engine phases
|
|
26
|
+
- get_feed — turn event stream (Fed, macro, rent, price moves, debt service, turn summary)
|
|
27
|
+
- add_bot — add LLM bot to lobby (strategies: aggressive, conservative, balanced, momentum, income, value_add)
|
|
28
|
+
- remove_bot — remove bot from lobby
|
|
29
|
+
- start_autonomous — enable auto-advance (advances on all-ready or turn deadline)
|
|
30
|
+
- stop_autonomous — pause auto-advance
|
|
31
|
+
- set_delegate — opt in to agent delegation when idle
|
|
32
|
+
- spectate — public game snapshot (no auth required)
|
|
33
|
+
|
|
34
|
+
### Market & Intel
|
|
35
|
+
- list_properties — active property pool with grades
|
|
36
|
+
- get_market_summary — live cap rates, price delta this turn, grade, vacancy, mechanics lien status
|
|
37
|
+
- get_fed_history — FOMC decision log with bps moves and mortgage rate impact
|
|
38
|
+
- get_player_actions — human-readable transaction timeline for a player
|
|
39
|
+
|
|
40
|
+
### Trading
|
|
41
|
+
- buy_tokens — all-cash token purchase at current market price
|
|
42
|
+
- sell_tokens — sell tokens (proceeds pay mechanics lien → first lien → HELOC/PACE before cash credited)
|
|
43
|
+
|
|
44
|
+
### Debt
|
|
45
|
+
- originate_mortgage — leveraged buy (tier-adjusted LTV and rate applied automatically)
|
|
46
|
+
- refi_mortgage — rate-and-term or cash-out refi
|
|
47
|
+
- heloc_draw — draw from home equity line of credit
|
|
48
|
+
- heloc_repay — repay HELOC balance
|
|
49
|
+
- prepay_principal — partial/full principal prepayment (works for first_lien, heloc, pace, mechanics_lien)
|
|
50
|
+
- improve_property — cash-funded grade upgrade (cost = steps × 8% × price, price bumps 5%/step)
|
|
51
|
+
- originate_pace_lien — financed grade upgrade, no down payment (rate = base + 1.5%)
|
|
52
|
+
- get_debt — all active mortgages: balances, LTV, arrears, type
|
|
53
|
+
|
|
54
|
+
### Portfolio
|
|
55
|
+
- get_portfolio — NAV, holdings with grade/yield/turns_held, investor tier, leverage ratio
|
|
56
|
+
- get_leaderboard — game or global leaderboard ranked by NAV with tier names
|
|
57
|
+
|
|
58
|
+
## Turn phases (in order)
|
|
59
|
+
1. fed_meeting — hike/cut/hold; FED_WARNING fires 1 turn before; ARMs reprice immediately
|
|
60
|
+
2. macro_events — rate macros take effect (1-turn warning system); active macros tick down
|
|
61
|
+
3. rent_collect — proportional to tokens; grade multipliers applied; vacancy blocks
|
|
62
|
+
4. random_events — vacancy, lease renewal, capex, appreciation/depreciation (all grade-adjusted)
|
|
63
|
+
5. market_move — applies price drift; optional live AVM blend
|
|
64
|
+
6. debt_service — collect monthly payments; forced sale after 1 grace turn
|
|
65
|
+
7. distribute — credits rent to balances; emits TURN_SUMMARY event
|
|
66
|
+
|
|
67
|
+
## Property grades
|
|
68
|
+
A (excellent) → B (good) → C (average) → D (below avg) → F (distressed)
|
|
69
|
+
Grade affects: rent multiplier, appreciation probability/range, capex risk, vacancy probability.
|
|
70
|
+
Upgrade via improve_property (cash) or originate_pace_lien (financed).
|
|
71
|
+
|
|
72
|
+
## Investor tiers (live from NAV, auto-applied to mortgage terms)
|
|
73
|
+
Retail (<$100k) → Accredited ($100k) → Professional ($500k) → Institutional ($2.5M) → Developer ($25M)
|
|
74
|
+
Higher tiers: +5% LTV and -25bps rate per tier step.
|
|
75
|
+
|
|
76
|
+
## Docs
|
|
77
|
+
https://sandbox.rentline.xyz
|
|
78
|
+
https://sandbox-api.rentline.xyz/docs
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rentline-sandbox",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "CLI and MCP server for the Rentline Sandbox real estate investment simulation game",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sandbox": "dist/index.js",
|
|
8
|
+
"sandbox-mcp": "dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./dist/index.js",
|
|
13
|
+
"./server": "./dist/server.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"SKILL.md",
|
|
24
|
+
"llms.txt",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.10.0",
|
|
29
|
+
"commander": "^13.0.0",
|
|
30
|
+
"dotenv": "^16.4.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.0.0",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
41
|
+
"author": "Brandyn Hamilton",
|
|
42
|
+
"homepage": "https://sandbox.rentline.xyz",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/BrandynHamilton/rentline-sandbox.git",
|
|
46
|
+
"directory": "sandbox-cli"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/BrandynHamilton/rentline-sandbox/issues"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"rentline",
|
|
56
|
+
"sandbox",
|
|
57
|
+
"real-estate",
|
|
58
|
+
"simulation",
|
|
59
|
+
"mcp",
|
|
60
|
+
"cli",
|
|
61
|
+
"model-context-protocol",
|
|
62
|
+
"ai-agent",
|
|
63
|
+
"game"
|
|
64
|
+
]
|
|
65
|
+
}
|