Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,25 @@ const home = resolveHome();

// Hermetic installs run from a bundled runtime where the backend lives outside
// projectRoot; the launcher points PODCLI_BACKEND at it.
const backendDir = process.env.PODCLI_BACKEND
? resolve(process.env.PODCLI_BACKEND)
: join(projectRoot, "backend");
function resolveBackendDir(): string {
if (process.env.PODCLI_BACKEND) {
return resolve(process.env.PODCLI_BACKEND);
}

const sourceBackend = join(projectRoot, "backend");
if (existsSync(join(sourceBackend, "cli.py"))) {
return sourceBackend;
}

const runtimeBackend = join(projectRoot, "runtime", "backend");
if (existsSync(join(runtimeBackend, "cli.py"))) {
return runtimeBackend;
}

return sourceBackend;
}

const backendDir = resolveBackendDir();

function detectPython(): string {
if (process.env.PYTHON_PATH) return process.env.PYTHON_PATH;
Expand Down
13 changes: 10 additions & 3 deletions src/handlers/integrations.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,18 @@ export function registerConfigIntegrationRoutes(
});

app.get("/api/integration-info", (_req, res) => {
const distPath = join(projectRoot, "dist", "index.js");
const packagedMcpPath = join(projectRoot, "runtime", "studio", "mcp-server.mjs");
const sourceMcpPath = join(projectRoot, "dist", "index.js");
const mcpPath = process.env.PODCLI_STUDIO
? join(process.env.PODCLI_STUDIO, "mcp-server.mjs")
: existsSync(packagedMcpPath)
? packagedMcpPath
: sourceMcpPath;
res.json({
dist_path: distPath,
mcp_path: mcpPath,
dist_path: mcpPath,
project_root: projectRoot,
server_ok: existsSync(distPath),
server_ok: existsSync(mcpPath),
});
});
}
21 changes: 8 additions & 13 deletions src/ui/client/ConfigPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,7 @@ export default function ConfigPage() {
const rows: Array<[string, string]> = status
? [
["Config home", status.home || ""],
["Data / cache", status.cache || ""],
["Profile marker", status.profile_marker || ""],
["Migration", status.migration?.already_migrated ? "Up to date" : "Ran on load"],
["Cache", status.cache || ""],
]
: [];

Expand Down Expand Up @@ -117,13 +115,13 @@ export default function ConfigPage() {
<label className="field-label">
{s.label}{" "}
<span style={{ color: s.set ? "var(--green)" : "var(--text3)", fontSize: 11 }}>
{s.set ? `set · ${s.preview}` : "not set"}
{s.set ? (s.preview || "set") : "not set"}
</span>
</label>
<div className="set-file">
<input
type="password"
placeholder={s.set ? "Enter a new value to replace" : "e.g. hf_…"}
placeholder={s.set ? "Replace token" : "hf_..."}
value={secretInputs[s.key] ?? ""}
onChange={(e) => setSecretInputs((p) => ({ ...p, [s.key]: e.target.value }))}
style={{ fontSize: 13, flex: 1 }}
Expand All @@ -137,10 +135,7 @@ export default function ConfigPage() {
{savingKey === s.key ? "Saving…" : "Save"}
</button>
</div>
<div style={{ fontSize: 12, color: "var(--text2)", marginTop: 6 }}>
{s.help}{" "}
<a href={s.url} target="_blank" rel="noopener" style={{ color: "var(--accent)" }}>Get token →</a>
</div>
<a href={s.url} target="_blank" rel="noopener" className="set-link">Get token</a>
</div>
))}
</div>
Expand All @@ -149,12 +144,12 @@ export default function ConfigPage() {
<div className="section">
<div className="section-label">Actions</div>
<div className="set-actions">
<button type="button" className="btn btn-ghost btn-sm" onClick={onMigrate}>Run path migration</button>
<button type="button" className="btn btn-primary btn-sm" onClick={onExport}>Download profile (.zip)</button>
<button type="button" className="btn btn-primary btn-sm" onClick={onExport}>Export profile</button>
<button type="button" className="btn btn-ghost btn-sm" onClick={onMigrate}>Migrate paths</button>
</div>

<div style={{ marginTop: 18 }}>
<label className="field-label">Import profile bundle</label>
<label className="field-label">Import profile</label>
<div className="set-file">
<input
type="file"
Expand All @@ -169,7 +164,7 @@ export default function ConfigPage() {
</div>
<label style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 10, fontSize: 12, color: "var(--text2)" }}>
<input type="checkbox" checked={activate} onChange={(e) => setActivate(e.target.checked)} />
Set imported folder as active config home
Use imported profile
</label>
</div>

Expand Down
38 changes: 19 additions & 19 deletions src/ui/client/McpSetupPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ const STATUS_STYLE: Record<StatusKind, React.CSSProperties> = {
};

export default function McpSetupPage() {
const [distPath, setDistPath] = useState<string | null>(null);
const [mcpPath, setMcpPath] = useState<string | null>(null);
const [statusKind, setStatusKind] = useState<StatusKind>("warn");
const [statusText, setStatusText] = useState("Checking connection…");
const [statusText, setStatusText] = useState("Checking…");
const desktopRef = useRef<HTMLPreElement>(null);
const codeRef = useRef<HTMLPreElement>(null);
const [copied, setCopied] = useState<Record<string, boolean>>({});

useEffect(() => {
api<any>("/integration-info")
.then((data) => {
setDistPath(data.dist_path);
setMcpPath(data.mcp_path || data.dist_path);
if (data.server_ok) {
setStatusKind("ok");
setStatusText(`Server running · ${data.tools_count} tools available`);
setStatusText("Ready");
} else {
setStatusKind("err");
setStatusText("Server not built — run: npm run build");
setStatusText("Not built");
}
})
.catch(() => {
Expand All @@ -42,15 +42,19 @@ export default function McpSetupPage() {
});
}

const dist = distPath ?? "<path-to>/dist/index.js";
const desktopJson = `{
"mcpServers": {
"podcli": {
"command": "node",
"args": ["${dist}"]
}
}
}`;
const serverPath = mcpPath ?? "<path-to>/mcp-server.mjs";
const desktopJson = JSON.stringify(
{
mcpServers: {
podcli: {
command: "node",
args: [serverPath],
},
},
},
null,
2
);

return (
<div className="app" style={{ maxWidth: 780 }}>
Expand All @@ -60,10 +64,6 @@ export default function McpSetupPage() {

<div className="section" style={{ marginTop: 18 }}>
<div className="section-label">Claude Desktop</div>
<div style={{ fontSize: 12.5, color: "var(--text2)", lineHeight: 1.6 }}>
Add to <code>~/Library/Application Support/Claude/claude_desktop_config.json</code> (macOS) or
<code> %APPDATA%\Claude\claude_desktop_config.json</code> (Windows), then restart Claude.
</div>
<div className="code-block">
<div className="code-block-head">
<span>claude_desktop_config.json</span>
Expand All @@ -84,7 +84,7 @@ export default function McpSetupPage() {
{copied.code ? "Copied" : "Copy"}
</button>
</div>
<pre ref={codeRef}>{`claude mcp add podcli node ${dist}`}</pre>
<pre ref={codeRef}>podcli mcp install</pre>
</div>
</div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions src/ui/public/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -394,13 +394,13 @@ h1 { font-size: 28px; font-weight: 700; letter-spacing: -0.5px; line-height: 1.1
.section-label { font-size: 11px; font-weight: 700; letter-spacing: 0.8px; color: var(--text2); margin-bottom: 10px; text-transform: uppercase; }

/* ─── Form Elements ─── */
input[type="text"], input[type="number"], select, textarea {
input[type="text"], input[type="number"], input[type="password"], select, textarea {
width: 100%; background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 11px 14px;
color: var(--text); font-family: inherit; font-size: 14px;
outline: none; transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:focus {
input[type="text"]:focus, input[type="number"]:focus, input[type="password"]:focus, select:focus, textarea:focus {
border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-subtle);
}
textarea {
Expand Down Expand Up @@ -1078,6 +1078,8 @@ input[type="range"]::-webkit-slider-thumb {
.set-note.ok { background: var(--green-subtle); color: var(--green); }
.set-note.err { background: var(--red-subtle); color: var(--red); }
.set-file { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.set-link { display: inline-block; margin-top: 6px; font-size: 12px; color: var(--accent); text-decoration: none; }
.set-link:hover { color: var(--accent-hover); }
.int-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--border); }
.int-row:last-child { border-bottom: none; }
.int-row .name { font-size: 14px; font-weight: 600; }
Expand Down
Loading