rdsh 0.5.2

Rust port/shim for dsh: fast launcher, token saver, zero behavior breakage via passthrough
<!DOCTYPE html>
<html lang="__RDSH_LOCALE__" data-rdsh-locale="__RDSH_LOCALE__">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>rdsh setup</title>
<link rel="icon" type="image/png" href="/icon.png">
<link rel="apple-touch-icon" href="/icon.png">
<style>
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Hiragino Sans", "Yu Gothic", system-ui, sans-serif; background: #000; color: #f5f5f7; -webkit-font-smoothing: antialiased; }
.stage { position: fixed; inset: 0; padding: 0; }
.app { display: flex; height: 100vh; background: #0b0b0d; color: #e8e8ea; filter: blur(2px) brightness(.72); transform: scale(.99); }
.side { width: 250px; flex: none; background: #131316; border-right: 1px solid rgba(255,255,255,.07); padding: 14px 12px; font-size: 12.5px; }
.logo { display: flex; align-items: center; gap: 8px; padding: 2px 8px 14px; font-weight: 700; font-size: 14px; }
.chip { font-size: 9px; font-weight: 700; border: 1px solid rgba(255,255,255,.25); border-radius: 4px; padding: 1px 5px; color: rgba(255,255,255,.75); }
.newbtn { border: 1px solid rgba(255,255,255,.16); border-radius: 9px; padding: 8px; text-align: center; color: rgba(255,255,255,.85); margin-bottom: 14px; }
.nav { padding: 8px; color: rgba(255,255,255,.75); }
.sect { padding: 12px 8px 6px; font-size: 11px; color: rgba(255,255,255,.42); }
.srow { padding: 7px 8px; border-radius: 8px; color: rgba(255,255,255,.66); white-space: nowrap; overflow: hidden; }
.srow.on { background: rgba(255,255,255,.09); color: #fff; }
.main { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 0 5vw; }
.hero { font-size: 26px; font-weight: 700; margin: 0 0 26px; }
.hero em { font-style: normal; font-size: 11px; vertical-align: middle; border: 1px solid rgba(122,162,247,.5); color: #7aa2f7; border-radius: 20px; padding: 2px 10px; margin-left: 10px; }
.ask { width: min(860px, 100%); background: #1c1c20; border: 1px solid rgba(255,255,255,.09); border-radius: 16px; padding: 14px 18px; }
.ask .ph { font-size: 13px; color: rgba(255,255,255,.35); }
.ask .bar { display: flex; align-items: center; margin-top: 26px; font-size: 12px; color: rgba(255,255,255,.6); }
.send { margin-left: auto; width: 30px; height: 30px; border-radius: 50%; background: #0a84ff; }
.scrim { position: fixed; inset: 0; background: rgba(0,0,0,.28); }
.card { position: relative; z-index: 2; max-width: 560px; margin: 9vh auto; padding: 40px 40px 32px; border-radius: 22px; background: rgba(28,28,30,.66); -webkit-backdrop-filter: blur(26px) saturate(1.4); backdrop-filter: blur(26px) saturate(1.4); border: 1px solid rgba(255,255,255,.14); box-shadow: 0 24px 70px rgba(0,0,0,.4), inset 0 1px 0 rgba(255,255,255,.12); animation: rise .55s cubic-bezier(.2,.8,.25,1); }
@keyframes rise { from { opacity: 0; transform: translateY(14px); } }
h1 { margin: 0 0 6px; font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
.sub { margin: 0 0 22px; font-size: 13px; color: rgba(235,235,245,.6); }
.okbar { display: none; margin: 0 0 18px; padding: 11px 16px; border-radius: 12px; font-size: 13px; font-weight: 600; color: #30d158; background: rgba(48,209,88,.12); border: 1px solid rgba(48,209,88,.32); }
.row { display: flex; align-items: center; gap: 10px; padding: 11px 2px; font-size: 13.5px; border-bottom: 1px solid rgba(255,255,255,.09); }
.dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.on { background: #30d158; }
.off { background: rgba(235,235,245,.28); }
.row small { margin-left: auto; font-size: 11.5px; color: rgba(235,235,245,.5); }
h3 { font-size: 12px; font-weight: 600; letter-spacing: .05em; margin: 24px 0 6px; color: rgba(235,235,245,.55); }
.cmd { display: flex; align-items: center; gap: 10px; padding: 10px 2px; border-bottom: 1px solid rgba(255,255,255,.09); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; }
.cmd code { flex: 1; overflow-x: auto; white-space: nowrap; }
.link { background: none; border: 0; padding: 4px 2px; font-size: 12.5px; font-weight: 500; color: #0a84ff; cursor: pointer; }
.field { display: flex; gap: 10px; padding: 12px 0 2px; }
input[type=password] { flex: 1; background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.14); border-radius: 10px; padding: 9px 12px; color: #fff; font-size: 13px; outline: none; }
input[type=password]:focus { border-color: rgba(10,132,255,.7); }
button.primary { border: 0; border-radius: 10px; padding: 9px 18px; font-size: 13px; font-weight: 600; color: #fff; background: #0a84ff; cursor: pointer; }
button.primary:active { transform: scale(.98); }
button.ghost { background: none; border: 1px solid rgba(255,255,255,.16); border-radius: 10px; padding: 8px 16px; font-size: 13px; color: #f5f5f7; cursor: pointer; }
.hint { font-size: 12px; color: rgba(235,235,245,.5); margin: 10px 0 0; overflow-wrap: anywhere; }
.foot { display: flex; gap: 10px; margin-top: 28px; }
@media (prefers-color-scheme: light) {
  body { color: #1d1d1f; }
  .app { filter: blur(2px) brightness(.9); }
  .scrim { background: rgba(255,255,255,.3); }
  .card { background: rgba(255,255,255,.7); border-color: rgba(0,0,0,.08); box-shadow: 0 24px 70px rgba(0,0,0,.16), inset 0 1px 0 #fff; }
  .sub,.row small,h3,.hint { color: rgba(60,60,67,.72); }
  .row,.cmd { border-color: rgba(0,0,0,.09); }
  .off { background: rgba(60,60,67,.3); }
  input[type=password] { background: rgba(255,255,255,.75); color: #1d1d1f; border-color: rgba(0,0,0,.12); }
  button.ghost { border-color: rgba(0,0,0,.14); color: #1d1d1f; }
  input[type=password] { background: rgba(255,255,255,.75); color: #1d1d1f; border-color: rgba(0,0,0,.12); }
}
</style>
</head>
<body>
<div class="stage"><div class="app"><div class="side"><div class="logo"><img src="/icon.png" width="28" height="28" alt="rdsh"> rdsh <span class="chip" id="mock-setup">SETUP</span></div><div class="newbtn" id="mock-new">+ 新しいセッション</div><div class="nav" id="mock-plugins">プラグイン</div><div class="sect" id="mock-workspaces">ワークスペース</div><div class="srow" id="mock-sample">適当</div><div class="srow on">rustdsh</div><div class="srow" id="mock-status">dshの現在の状況を確認</div><div class="srow">Flaxia</div><div class="sect" id="mock-settings">設定</div></div><div class="main"><p class="hero"><span id="mock-hero">新しい発見へ</span> <em id="mock-preview">プレビュー</em></p><div class="ask"><div class="ph" id="mock-prompt">作りたいものを説明してください(/コマンド、@ファイル/セッション)</div><div class="bar"><span id="mock-access">+ Full access</span><span style="margin-left:auto">Muse Spark 1.3 Contributor</span><span class="send"></span></div></div></div></div></div>
<div class="scrim"></div>
<div class="card">
<img src="/icon.png" width="44" height="44" alt="rdsh">
<h1 id="t">モデル接続</h1>
<p class="sub" id="s">Codex / opencode のログインから、共有する認証情報を選びます</p>
<div class="okbar" id="okbar"></div>
<div id="token-box" class="field" style="display:none"><input type="password" id="setup-token" autocomplete="off" spellcheck="false" placeholder="保護済みトークンファイルの内容"><button class="primary" id="setup-connect" onclick="connectSetup()">接続</button></div>
<p class="hint" id="token-hint" style="display:none">端末に表示された保護済みトークンファイルの内容を入力してください。トークンはアドレスバーに保存されません。</p>
<div id="list"></div>
<h3 id="h0">dshが使うモデル</h3>
<div id="defs"></div>
<h3 id="hs">共有する認証情報を選ぶ</h3>
<div id="sharing"></div>
<p class="hint" id="sharehint"></p>
<h3 id="hx">追加機能(任意)</h3>
<div id="extras"></div>
<p class="hint" id="khx">普段は不要です。必要になったものだけ有効にできます</p>
<h3 id="h1">サブスクで使う — APIキー不要</h3>
<div class="cmd"><code>opencode auth login</code><button class="link" id="copy-opencode" onclick="cp(this)">コピー</button></div>
<div class="cmd"><code>codex login</code><button class="link" id="copy-codex" onclick="cp(this)">コピー</button></div>
<h3 id="h2">DeepSeekキーを使う</h3>
<div class="field"><input type="password" id="key" placeholder="DEEPSEEK_API_KEY"><button class="primary" id="save-key" onclick="setupAction(save)">保存</button></div>
<p class="hint" id="kh">保存すると、表示した認証ファイルから全DSHプロファイルで利用できます。利用を止めるにはDSHを停止して該当refを削除し、キーの失効は発行元で行います。</p>
<div><button class="ghost" id="issue" onclick="window.open('https://platform.deepseek.com','_blank','noopener')">キーを発行する</button></div>
<div class="foot"><button class="ghost" id="refresh" onclick="setupAction(load)">更新</button><button class="ghost" id="done" onclick="setupAction(bye)" style="display:none">閉じる</button></div>
</div>
<script>
var setupToken = new URLSearchParams(location.hash.slice(1)).get("key") || sessionStorage.getItem("rdsh_setup_token") || "";
if (setupToken) sessionStorage.setItem("rdsh_setup_token", setupToken);
history.replaceState(null, "", location.pathname);
var LANG = document.documentElement.dataset.rdshLocale || "en";
var JA = LANG === "ja";
var ZH = LANG === "zh-CN";
function t(ja, en, zh){ return JA ? ja : (ZH ? zh : en); }
function text(id, ja, en, zh){ document.getElementById(id).textContent = t(ja, en, zh); }
text("t", "モデルを接続", "Connect a model", "连接模型"); text("s", "共有する Codex / opencode 認証情報を選びます", "Choose which Codex / opencode credentials to share", "选择要共享的 Codex / opencode 凭据"); text("h1", "サブスク — APIキー不要", "Subscription — no API key needed", "订阅 — 无需 API 密钥"); text("h2", "DeepSeekキーを使う", "Use a DeepSeek key", "使用 DeepSeek 密钥"); text("kh", "保存すると全DSHプロファイルで利用できます。利用を止めるにはDSHを停止して該当refを削除し、失効は発行元で行います。", "Saving persists this key for every DSH profile. Stop DSH and remove the ref to stop using the copy; revoke the key at its provider.", "保存后所有 DSH 配置文件都可使用此密钥。要停止使用副本,请停止 DSH 并删除对应 ref;请在提供方撤销密钥。"); text("h0", "dshが使用するモデル", "Models dsh will use", "dsh 将使用的模型"); text("hx", "追加機能(任意)", "Extras (optional)", "附加功能(可选)"); text("khx", "通常は不要です。必要な機能だけ有効にしてください。", "Usually unnecessary. Enable only what you need.", "通常不需要。只启用所需的功能。"); text("issue", "キーを発行する", "Get a key", "获取密钥"); text("mock-new", "+ 新しいセッション", "+ New session", "+ 新建会话"); text("mock-plugins", "プラグイン", "Plugins", "插件"); text("mock-workspaces", "ワークスペース", "WORKSPACES", "工作区"); text("mock-sample", "適当", "Sample", "示例"); text("mock-status", "dshの現在の状況を確認", "Check dsh status", "检查 dsh 状态"); text("mock-settings", "設定", "Settings", "设置"); text("mock-hero", "新しい発見へ", "Discover what is next", "探索新发现"); text("mock-preview", "プレビュー", "Preview", "预览"); text("mock-prompt", "作りたいものを説明してください(/コマンド、@ファイル/セッション)", "Describe what you want to make (/ commands, @ files/sessions)", "描述你想创建的内容(/ 命令、@ 文件/会话)"); document.getElementById("setup-token").placeholder = t("保護済みトークンファイルの内容", "Protected token file contents", "受保护令牌文件的内容"); document.getElementById("token-hint").textContent = t("端末に表示された保護済みトークンファイルの内容を入力してください。トークンはアドレスバーに保存されません。", "Paste the contents of the protected token file printed in the terminal. The token is not stored in the address bar.", "粘贴终端中显示的受保护令牌文件内容。令牌不会保存在地址栏中。");
text("hs", "共有する認証情報を選ぶ", "Choose credentials to share", "选择要共享的凭据");
text("mock-setup", "設定", "SETUP", "设置");
text("mock-access", "+ 完全アクセス", "+ Full access", "+ 完全访问");
text("setup-connect", "接続", "Connect", "连接");
text("copy-opencode", "コピー", "Copy", "复制");
text("copy-codex", "コピー", "Copy", "复制");
text("save-key", "保存", "Save", "保存");
text("refresh", "更新", "Refresh", "刷新");
text("done", "閉じる", "Close", "关闭");
function statusLabel(value) {
  const labels = {ok:["接続済み","Connected","已连接"],import:["インポート","Import","导入"],refresh:["更新","Refresh","刷新"],missing:["未設定","Not configured","未配置"],invalid:["無効","Invalid","无效"],unavailable:["未取得","Unavailable","未获取"]};
  return labels[value] ? t(...labels[value]) : value;
}
function showSetupError(error) { const bar=document.getElementById("okbar"); bar.style.display="block"; bar.textContent=t("操作に失敗しました: ","Operation failed: ","操作失败:")+error.message; }
async function setupAction(action) { try { await action(); } catch(error) { showSetupError(error); } }
function cp(b){ var value = b.parentNode.querySelector("code").textContent; function ok(){ b.textContent = t("済", "done", "已复制"); setTimeout(function(){ b.textContent = t("コピー", "copy", "复制"); }, 1200); } if (navigator.clipboard) { navigator.clipboard.writeText(value).then(ok, function(){ prompt(t("コピー:", "copy:", "复制:"), value); }); } else { prompt(t("コピー:", "copy:", "复制:"), value); } }
async function connectSetup(candidateInput){ var candidate = typeof candidateInput === "string" ? candidateInput : document.getElementById("setup-token").value.trim(); var warning = document.getElementById("okbar"); try { var response = await fetch("/api/status", {headers: {"X-RDSH-Token": candidate}}); if (!response.ok) throw new Error(t("トークンを確認できません。ファイルの内容を確認してください。", "Token rejected. Check the handoff file contents.", "令牌被拒绝。请检查交接文件内容。")); setupToken = candidate; sessionStorage.setItem("rdsh_setup_token", setupToken); document.getElementById("setup-token").value = ""; document.getElementById("token-box").style.display = "none"; document.getElementById("token-hint").style.display = "none"; warning.style.display = "none"; await load(); await loadExtras(); startRefresh(); } catch(e) { warning.style.display = "block"; warning.textContent = e.message; document.getElementById("token-box").style.display = "flex"; document.getElementById("token-hint").style.display = "block"; } }
async function load(){ var response = await fetch("/api/status", {headers: {"X-RDSH-Token": setupToken}}); if (!response.ok) { var warning = document.getElementById("okbar"); warning.style.display = "block"; warning.textContent = t("保護済みトークンファイルの内容を入力してください", "Enter the contents of the protected token handoff file", "请输入受保护令牌交接文件的内容"); document.getElementById("token-box").style.display = "flex"; document.getElementById("token-hint").style.display = "block"; return; } var r = await response.json(); var el = document.getElementById("list"); el.innerHTML = ""; (r.records || []).forEach(function(x){ var d = document.createElement("div"); d.className = "row"; var good = x.action === "ok"; var dot = document.createElement("span"); dot.className = "dot " + (good ? "on" : "off"); var nm = document.createElement("span"); nm.textContent = x.provider; var sm = document.createElement("small"); sm.textContent = x.from + " / " + statusLabel(x.action); d.appendChild(dot); d.appendChild(nm); d.appendChild(sm); el.appendChild(d); }); showSharing(r.sharing || {}); var df = document.getElementById("defs"); df.innerHTML = ""; (r.defaults || []).forEach(function(x){ var d = document.createElement("div"); d.className = "row"; var good = x.state === "ok"; var dot = document.createElement("span"); dot.className = "dot " + (good ? "on" : "off"); var nm = document.createElement("span"); nm.textContent = x.profile + " " + x.provider + "/" + x.model; var sm = document.createElement("small"); sm.textContent = x.via + " / " + statusLabel(x.state); d.appendChild(dot); d.appendChild(nm); d.appendChild(sm); df.appendChild(d); }); var bar = document.getElementById("okbar"); if (!r.needed) { bar.style.display = "block"; bar.textContent = t("接続OK — この画面は閉じてOKです", "Connected — you can close this page", "已连接 — 可以关闭此页面"); document.getElementById("done").style.display = ""; } else { bar.style.display = "none"; document.getElementById("done").style.display = "none"; } }
var EXTRAS = [{id: "serve", ja: "ダッシュボード(rdsh serve)", en: "dashboard (rdsh serve)", zh: "仪表板(rdsh serve)"}, {id: "search-web", ja: "Web検索(rdsh search-web)", en: "web search (rdsh search-web)", zh: "网页搜索(rdsh search-web)"}];
async function loadExtras(){ var response = await fetch("/api/extras", {headers: {"X-RDSH-Token": setupToken}}); if (!response.ok) return; var r = await response.json(); var on = (r && r.enable) || []; var el = document.getElementById("extras"); el.innerHTML = ""; EXTRAS.forEach(function(x){ var d = document.createElement("div"); d.className = "row"; var good = on.indexOf(x.id) >= 0; var dot = document.createElement("span"); dot.className = "dot " + (good ? "on" : "off"); var nm = document.createElement("span"); nm.textContent = t(x.ja, x.en, x.zh); var sm = document.createElement("small"); sm.textContent = good ? t("有効", "on", "已启用") : t("無効", "off", "已禁用"); d.appendChild(dot); d.appendChild(nm); d.appendChild(sm); d.style.cursor = "pointer"; d.onclick = function(){ setupAction(function(){ return toggleExtra(x.id, !good); }); }; el.appendChild(d); }); }
function sharingPolicyError(value) {
  const messages = {
    "credential sharing policy is unreadable; imports are disabled": ["認証情報の共有ポリシーを読み取れません。インポートは無効です", "credential sharing policy is unreadable; imports are disabled", "无法读取凭据共享策略;已禁用导入"],
    "credential sharing policy is oversized; imports are disabled": ["認証情報の共有ポリシーが大きすぎます。インポートは無効です", "credential sharing policy is oversized; imports are disabled", "凭据共享策略过大;已禁用导入"],
    "invalid credential sharing policy; imports are disabled": ["認証情報の共有ポリシーが無効です。インポートは無効です", "invalid credential sharing policy; imports are disabled", "凭据共享策略无效;已禁用导入"],
    "unsupported credential sharing policy; imports are disabled": ["認証情報の共有ポリシーは未対応です。インポートは無効です", "unsupported credential sharing policy; imports are disabled", "凭据共享策略不受支持;已禁用导入"],
  };
  return Object.hasOwn(messages, value) ? t(...messages[value]) : value;
}
function showSharing(sharing) {
  var list = document.getElementById("sharing");
  list.replaceChildren();
  var hint = document.getElementById("sharehint");
  hint.textContent = t("永続保存先: ", "Persistent destination: ", "持久保存位置:") + (sharing.persistent_destination || "") + t("。保存内容は同じDSH_HOMEの全プロファイルで利用できます。選択解除は今後の取込を止め、保存済みのコピーは残します。利用を止めるにはDSHを停止して該当record/refを削除します。発行元での失効は別途必要です。", ". Copies are available to every profile in this DSH_HOME. Unselect stops future imports and retains existing copies. Stop DSH and remove the named record/ref to stop using a copy. Provider revocation is separate.", "。此 DSH_HOME 中所有配置文件都可使用副本。取消选择会停止将来导入,并保留现有副本。要停止使用副本,请停止 DSH 并删除对应 record/ref。在提供方撤销需要单独进行。");
  if (sharing.policy_error) {
    hint.textContent += " " + sharingPolicyError(sharing.policy_error);
    return;
  }
  var seen = new Set();
  (sharing.inventory || []).forEach(function(item) {
    if (seen.has(item.selector)) return;
    seen.add(item.selector);
    var row = document.createElement("div");
    row.className = "cmd";
    var code = document.createElement("code");
    code.textContent = "rdsh auth --" + (item.selected ? "unselect " : "select ") + item.selector + (item.selected ? "" : " --import");
    var button = document.createElement("button");
    button.className = "link";
    button.textContent = t("コピー", "Copy", "复制");
    button.onclick = function() { cp(button); };
    row.append(code, button);
    list.appendChild(row);
  });
  (sharing.environment_references || []).forEach(function(item) {
    var note = document.createElement("p");
    note.className = "hint";
    note.textContent = item.name + t(": 環境変数として利用中。ファイル保存なし。DSHを停止し、次回起動前に環境変数を解除すると共有が止まります。", ": inherited environment reference; no file write. Stop DSH and unset it before the next launch to stop sharing.", ":继承的环境变量引用;未写入文件。要停止共享,请停止 DSH 并在下次启动前取消环境变量。");
    list.appendChild(note);
  });
}
async function toggleExtra(id, want){ var r = await (await fetch("/api/extras", {headers: {"X-RDSH-Token": setupToken}})).json(); var on = (r && r.enable) || []; if (want && on.indexOf(id) < 0) on.push(id); if (!want) on = on.filter(function(y){ return y !== id; }); await fetch("/api/extras", {method: "POST", headers: {"Content-Type": "application/json", "X-RDSH-Token": setupToken}, body: JSON.stringify({enable: on})}); loadExtras(); }
async function save(){ var v = document.getElementById("key").value; var r = await (await fetch("/api/key", {method: "POST", headers: {"Content-Type": "application/json", "X-RDSH-Token": setupToken}, body: JSON.stringify({name: "DEEPSEEK_API_KEY", value: v})})).json(); if (r.stored) { document.getElementById("key").value = ""; load(); } else { alert(t("保存に失敗しました: ", "save failed: ", "保存失败:") + (r.error || "?")); } }
async function bye(){ var r = await fetch("/api/done", {method: "POST", headers: {"X-RDSH-Token": setupToken}, body: "{}"}); if (!r.ok) return; document.body.innerHTML = "<p style=\"text-align:center;margin-top:22vh;opacity:.55\">" + t("完了 — このタブを閉じてください", "done — close this tab", "完成 — 请关闭此标签页") + "</p>"; }
function startRefresh(){ if (setupToken) setInterval(function(){ setupAction(load); setupAction(loadExtras); }, 5000); }
if (setupToken) connectSetup(setupToken); else { document.getElementById("token-box").style.display = "flex"; document.getElementById("token-hint").style.display = "block"; }
</script>
</body>
</html>