diff --git a/src/config/paths.ts b/src/config/paths.ts
index c9477d7..fc2eff0 100644
--- a/src/config/paths.ts
+++ b/src/config/paths.ts
@@ -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;
diff --git a/src/handlers/integrations.routes.ts b/src/handlers/integrations.routes.ts
index 5e1dc39..44949b8 100644
--- a/src/handlers/integrations.routes.ts
+++ b/src/handlers/integrations.routes.ts
@@ -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),
});
});
}
diff --git a/src/ui/client/ConfigPage.tsx b/src/ui/client/ConfigPage.tsx
index bac332e..dbd09bc 100644
--- a/src/ui/client/ConfigPage.tsx
+++ b/src/ui/client/ConfigPage.tsx
@@ -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 || ""],
]
: [];
@@ -117,13 +115,13 @@ export default function ConfigPage() {
{s.label}{" "}
- {s.set ? `set · ${s.preview}` : "not set"}
+ {s.set ? (s.preview || "set") : "not set"}
setSecretInputs((p) => ({ ...p, [s.key]: e.target.value }))}
style={{ fontSize: 13, flex: 1 }}
@@ -137,10 +135,7 @@ export default function ConfigPage() {
{savingKey === s.key ? "Saving…" : "Save"}
-
+ Get token
))}
@@ -149,12 +144,12 @@ export default function ConfigPage() {
Actions
- Run path migration
- Download profile (.zip)
+ Export profile
+ Migrate paths
-
Import profile bundle
+
Import profile
setActivate(e.target.checked)} />
- Set imported folder as active config home
+ Use imported profile
diff --git a/src/ui/client/McpSetupPage.tsx b/src/ui/client/McpSetupPage.tsx
index 635e8f3..ede9f4b 100644
--- a/src/ui/client/McpSetupPage.tsx
+++ b/src/ui/client/McpSetupPage.tsx
@@ -10,9 +10,9 @@ const STATUS_STYLE: Record
= {
};
export default function McpSetupPage() {
- const [distPath, setDistPath] = useState(null);
+ const [mcpPath, setMcpPath] = useState(null);
const [statusKind, setStatusKind] = useState("warn");
- const [statusText, setStatusText] = useState("Checking connection…");
+ const [statusText, setStatusText] = useState("Checking…");
const desktopRef = useRef(null);
const codeRef = useRef(null);
const [copied, setCopied] = useState>({});
@@ -20,13 +20,13 @@ export default function McpSetupPage() {
useEffect(() => {
api("/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(() => {
@@ -42,15 +42,19 @@ export default function McpSetupPage() {
});
}
- const dist = distPath ?? "/dist/index.js";
- const desktopJson = `{
- "mcpServers": {
- "podcli": {
- "command": "node",
- "args": ["${dist}"]
- }
- }
-}`;
+ const serverPath = mcpPath ?? "/mcp-server.mjs";
+ const desktopJson = JSON.stringify(
+ {
+ mcpServers: {
+ podcli: {
+ command: "node",
+ args: [serverPath],
+ },
+ },
+ },
+ null,
+ 2
+ );
return (
@@ -60,10 +64,6 @@ export default function McpSetupPage() {
Claude Desktop
-
- Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or
- %APPDATA%\Claude\claude_desktop_config.json (Windows), then restart Claude.
-
claude_desktop_config.json
@@ -84,7 +84,7 @@ export default function McpSetupPage() {
{copied.code ? "Copied" : "Copy"}
-
{`claude mcp add podcli node ${dist}`}
+
podcli mcp install
diff --git a/src/ui/public/css/styles.css b/src/ui/public/css/styles.css
index d5a95c9..22ec263 100644
--- a/src/ui/public/css/styles.css
+++ b/src/ui/public/css/styles.css
@@ -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 {
@@ -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; }