pi-workspace 0.1.0
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/.env.example +1 -0
- package/README.md +87 -0
- package/bin/pi-gui.mjs +127 -0
- package/dist/client/.vite/manifest.json +26 -0
- package/dist/client/assets/favicon-_Gh0MYzg.svg +8 -0
- package/dist/client/assets/icon-BBRFWKI9.svg +30 -0
- package/dist/client/assets/index-D24SA7pm.css +1 -0
- package/dist/client/assets/index-qtlLehV3.js +340 -0
- package/dist/client/index.html +15 -0
- package/dist-server/index.mjs +763 -0
- package/package.json +57 -0
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PORT=8787
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# pi-gui
|
|
2
|
+
|
|
3
|
+
`pi-gui` is a web-based agent conversation console built on the
|
|
4
|
+
`@earendil-works/pi-coding-agent` SDK. It provides a browser UI while keeping
|
|
5
|
+
model credentials and the Pi agent session runtime on the server.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Online chat interface for Pi-powered agent conversations
|
|
10
|
+
- Server-side `createAgentSession()` integration from `@earendil-works/pi-coding-agent`
|
|
11
|
+
- Streaming assistant responses over Server-Sent Events
|
|
12
|
+
- Model selector for common OpenAI, Anthropic, Google, Mistral, and Command Code models
|
|
13
|
+
- Editable system prompt
|
|
14
|
+
- Browser-side conversation transcript persistence
|
|
15
|
+
- Server-side model auth through local Pi and Command Code login state
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install
|
|
21
|
+
npm exec -- pi-gui
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If you already use Pi locally, the server reads your existing Pi auth from
|
|
25
|
+
`~/.pi/agent/auth.json` and custom models from `~/.pi/agent/models.json`.
|
|
26
|
+
It also detects Command Code CLI login credentials from
|
|
27
|
+
`~/.commandcode/auth.json`.
|
|
28
|
+
|
|
29
|
+
When Command Code auth is present, the server fetches live models from
|
|
30
|
+
`https://api.commandcode.ai/provider/v1/models` and registers them under the
|
|
31
|
+
`commandcode` provider.
|
|
32
|
+
|
|
33
|
+
If you want to customize the server port, you can still create a local `.env`
|
|
34
|
+
from `.env.example` and change `PORT`.
|
|
35
|
+
|
|
36
|
+
Start the packaged service:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm exec -- pi-gui
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Open <http://127.0.0.1:8787>.
|
|
43
|
+
|
|
44
|
+
Useful CLI commands:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm exec -- pi-gui # start the built service
|
|
48
|
+
npm exec -- pi-gui build # build client + server bundles
|
|
49
|
+
npm exec -- pi-gui --help # show all options
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## npm Packaging
|
|
53
|
+
|
|
54
|
+
`pi-gui` is set up as a publishable npm CLI package:
|
|
55
|
+
|
|
56
|
+
- `npm publish` runs `prepack`, which builds `dist/client` and `dist-server`
|
|
57
|
+
- the published tarball includes only the CLI entrypoint and built runtime assets
|
|
58
|
+
- `pi-gui` starts the bundled production server directly from `dist-server/index.mjs`
|
|
59
|
+
|
|
60
|
+
## Production
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run build
|
|
64
|
+
npm start
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The production server listens on `PORT` or `8787` and serves both the API and
|
|
68
|
+
the built frontend.
|
|
69
|
+
|
|
70
|
+
## Architecture
|
|
71
|
+
|
|
72
|
+
- `src/` contains the React conversation UI.
|
|
73
|
+
- `server/index.ts` owns the Pi Coding Agent SDK integration.
|
|
74
|
+
- The frontend sends only the latest user prompt and session metadata.
|
|
75
|
+
- The backend keeps a per-browser-session `AgentSession` in memory and streams
|
|
76
|
+
`message_update` deltas back to the browser.
|
|
77
|
+
- `AuthStorage.create()` and `ModelRegistry.create()` load the same local auth
|
|
78
|
+
and model registry that the Pi CLI uses.
|
|
79
|
+
|
|
80
|
+
By default the server starts Pi sessions with `noTools: "all"` so the online
|
|
81
|
+
chat cannot execute shell or file mutation tools. Add a deliberate tool allowlist
|
|
82
|
+
only after adding authentication and permission controls.
|
|
83
|
+
|
|
84
|
+
## Attribution
|
|
85
|
+
|
|
86
|
+
This project is customized from the public Pi ecosystem by Earendil Works:
|
|
87
|
+
<https://github.com/earendil-works/pi>.
|
package/bin/pi-gui.mjs
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const projectRoot = resolve(__dirname, "..");
|
|
10
|
+
const cliName = "pi-gui";
|
|
11
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
12
|
+
|
|
13
|
+
function printHelp() {
|
|
14
|
+
console.log(
|
|
15
|
+
[
|
|
16
|
+
`${cliName} - start the bundled pi-gui service`,
|
|
17
|
+
"",
|
|
18
|
+
"Usage:",
|
|
19
|
+
` ${cliName} Start the built service`,
|
|
20
|
+
` ${cliName} start Start the built production server`,
|
|
21
|
+
` ${cliName} build Build client and server bundles`,
|
|
22
|
+
` ${cliName} --help Show this help message`,
|
|
23
|
+
"",
|
|
24
|
+
"Options:",
|
|
25
|
+
" --port <number> Override PORT for the service"
|
|
26
|
+
].join("\n")
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseArgs(argv) {
|
|
31
|
+
let command = "start";
|
|
32
|
+
let port;
|
|
33
|
+
|
|
34
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
35
|
+
const arg = argv[index];
|
|
36
|
+
|
|
37
|
+
if (arg === "--help" || arg === "-h") {
|
|
38
|
+
command = "help";
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (arg === "--port") {
|
|
43
|
+
const value = argv[index + 1];
|
|
44
|
+
if (!value || value.startsWith("-")) {
|
|
45
|
+
throw new Error("Missing value for --port");
|
|
46
|
+
}
|
|
47
|
+
port = value;
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (arg === "start" || arg === "build" || arg === "help") {
|
|
53
|
+
command = arg;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { command, port };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function run(command, args, env = {}) {
|
|
64
|
+
return new Promise((resolvePromise, reject) => {
|
|
65
|
+
const child = spawn(command, args, {
|
|
66
|
+
cwd: projectRoot,
|
|
67
|
+
stdio: "inherit",
|
|
68
|
+
env: { ...process.env, ...env }
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
child.on("error", reject);
|
|
72
|
+
child.on("exit", (code) => {
|
|
73
|
+
if (code === 0) {
|
|
74
|
+
resolvePromise();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
reject(new Error(`${command} ${args.join(" ")} exited with code ${code ?? 1}`));
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function ensureBuild(env) {
|
|
84
|
+
const hasServerBuild = existsSync(resolve(projectRoot, "dist-server", "index.mjs"));
|
|
85
|
+
const hasClientBuild = existsSync(resolve(projectRoot, "dist", "client", "index.html"));
|
|
86
|
+
|
|
87
|
+
if (hasServerBuild && hasClientBuild) return;
|
|
88
|
+
|
|
89
|
+
const canBuildFromSource =
|
|
90
|
+
existsSync(resolve(projectRoot, "server")) &&
|
|
91
|
+
existsSync(resolve(projectRoot, "client")) &&
|
|
92
|
+
existsSync(resolve(projectRoot, "package.json"));
|
|
93
|
+
|
|
94
|
+
if (!canBuildFromSource) {
|
|
95
|
+
throw new Error("Bundled build output is missing from this package.");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(`[${cliName}] build output not found, running npm run build...`);
|
|
99
|
+
await run(npmCmd, ["run", "build"], env);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function main() {
|
|
103
|
+
const { command, port } = parseArgs(process.argv.slice(2));
|
|
104
|
+
|
|
105
|
+
if (command === "help") {
|
|
106
|
+
printHelp();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const env = port ? { PORT: port } : {};
|
|
111
|
+
|
|
112
|
+
if (command === "build") {
|
|
113
|
+
await run(npmCmd, ["run", "build"], env);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await ensureBuild(env);
|
|
118
|
+
await run(process.execPath, ["dist-server/index.mjs"], {
|
|
119
|
+
...env,
|
|
120
|
+
NODE_ENV: "production"
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
main().catch((error) => {
|
|
125
|
+
console.error(`[${cliName}] ${error instanceof Error ? error.message : String(error)}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"favicon.svg": {
|
|
3
|
+
"file": "assets/favicon-_Gh0MYzg.svg",
|
|
4
|
+
"src": "favicon.svg"
|
|
5
|
+
},
|
|
6
|
+
"icon.svg": {
|
|
7
|
+
"file": "assets/icon-BBRFWKI9.svg",
|
|
8
|
+
"src": "icon.svg"
|
|
9
|
+
},
|
|
10
|
+
"index.html": {
|
|
11
|
+
"file": "assets/index-qtlLehV3.js",
|
|
12
|
+
"name": "index",
|
|
13
|
+
"src": "index.html",
|
|
14
|
+
"isEntry": true,
|
|
15
|
+
"dynamicImports": [
|
|
16
|
+
"index.html"
|
|
17
|
+
],
|
|
18
|
+
"css": [
|
|
19
|
+
"assets/index-D24SA7pm.css"
|
|
20
|
+
],
|
|
21
|
+
"assets": [
|
|
22
|
+
"assets/favicon-_Gh0MYzg.svg",
|
|
23
|
+
"assets/icon-BBRFWKI9.svg"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
|
2
|
+
<rect x="0" y="0" width="32" height="32" rx="6" fill="#0075de"/>
|
|
3
|
+
<g transform="translate(16, 10)">
|
|
4
|
+
<line x1="-9" y1="0" x2="9" y2="0" stroke="#ffffff" stroke-width="3.5" stroke-linecap="round"/>
|
|
5
|
+
<line x1="-6" y1="0" x2="-6" y2="11" stroke="#ffffff" stroke-width="3.5" stroke-linecap="round"/>
|
|
6
|
+
<line x1="6" y1="0" x2="6" y2="11" stroke="#ffffff" stroke-width="3.5" stroke-linecap="round"/>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="128" y2="128" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop offset="0%" stop-color="#ffffff"/>
|
|
5
|
+
<stop offset="100%" stop-color="#f0f0f0"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="pi" x1="0" y1="0" x2="128" y2="128" gradientUnits="userSpaceOnUse">
|
|
8
|
+
<stop offset="0%" stop-color="#0075de"/>
|
|
9
|
+
<stop offset="100%" stop-color="#005bab"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<!-- Square background pill-like shape -->
|
|
13
|
+
<rect x="6" y="6" width="116" height="116" rx="24" fill="url(#bg)" stroke="#e0e0e0" stroke-width="2"/>
|
|
14
|
+
<!-- Right angle bracket / message pointer bottom-left -->
|
|
15
|
+
<path d="M30 92 L18 80 L30 80" fill="#eaeaea" stroke="#d6d6d6" stroke-width="1.5" stroke-linejoin="round"/>
|
|
16
|
+
<!-- π symbol -->
|
|
17
|
+
<g transform="translate(64, 42)">
|
|
18
|
+
<!-- Horizontal bar of π -->
|
|
19
|
+
<line x1="-32" y1="0" x2="32" y2="0" stroke="url(#pi)" stroke-width="10" stroke-linecap="round"/>
|
|
20
|
+
<!-- Left leg of π -->
|
|
21
|
+
<line x1="-20" y1="0" x2="-20" y2="36" stroke="url(#pi)" stroke-width="10" stroke-linecap="round"/>
|
|
22
|
+
<!-- Right leg of π -->
|
|
23
|
+
<line x1="20" y1="0" x2="20" y2="36" stroke="url(#pi)" stroke-width="10" stroke-linecap="round"/>
|
|
24
|
+
</g>
|
|
25
|
+
<!-- Small dots / particles suggesting conversation or AI -->
|
|
26
|
+
<circle cx="36" cy="82" r="3.5" fill="#a39e98" opacity="0.5"/>
|
|
27
|
+
<circle cx="50" cy="90" r="2.5" fill="#a39e98" opacity="0.35"/>
|
|
28
|
+
<circle cx="92" cy="82" r="3.5" fill="#a39e98" opacity="0.5"/>
|
|
29
|
+
<circle cx="78" cy="90" r="2.5" fill="#a39e98" opacity="0.35"/>
|
|
30
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--primary:#0075de;--primary-active:#005bab;--canvas:#fff;--canvas-soft:#f6f5f4;--hairline:#e6e6e6;--ink:#000;--ink-secondary:#31302e;--ink-muted:#615d59;--ink-faint:#a39e98;--accent-teal:#2a9d99;--sidebar-bg:#f0f0f0;--sidebar-text:#1a1a1a;--sidebar-text-muted:#6e6e6e;--sidebar-hover:#0000000a;--sidebar-active:#0075de14;--sidebar-border:#00000014;--card-tint-mint:#d9f3e1;--card-tint-sky:#dcecfa;--card-tint-yellow:#fef7d6;--semantic-error:#e03131;background:var(--canvas);color:var(--ink);font-synthesis:none;text-rendering:optimizelegibility;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}body{min-width:320px;min-height:100vh;margin:0}button,select,textarea{font:inherit}.app-shell{background:var(--canvas);grid-template-columns:372px minmax(0,1fr);min-height:100vh;transition:grid-template-columns .2s;display:grid}.app-shell-collapsed{grid-template-columns:0 1fr}.sidebar{border-right:1px solid var(--sidebar-border);background:var(--sidebar-bg);height:100vh;color:var(--sidebar-text);flex-direction:column;gap:12px;padding:16px;display:flex;position:relative;overflow:hidden}.sidebar>*{position:relative}.eyebrow{color:var(--ink-faint);letter-spacing:1px;text-transform:uppercase;margin:0 0 8px;font-size:11px;font-weight:600}.sidebar .eyebrow{color:var(--sidebar-text-muted)}h1,h2,h3,p{margin-top:0}h1{max-width:280px;margin-bottom:12px;font-size:42px;font-weight:600;line-height:1.1}h2{color:var(--ink-secondary);margin-bottom:0;font-size:28px;font-weight:600;line-height:1.25}.sidebar-copy{max-width:300px;color:var(--sidebar-text-muted);font-size:16px;line-height:1.55}.brand-block{flex:none}.field{color:var(--ink-secondary);gap:8px;font-size:14px;font-weight:500;display:grid}.sidebar .field{color:var(--sidebar-text)}.field select,.field textarea{border:1px solid var(--hairline);background:var(--canvas);width:100%;color:var(--ink);border-radius:8px;outline:none}.sidebar .field select,.sidebar .field textarea{border-color:var(--sidebar-border);color:var(--sidebar-text);background:#ffffff80}.sidebar .field select option{color:var(--ink)}.field select{min-height:42px;padding:0 12px}.field textarea{resize:vertical;padding:12px;line-height:1.5}.field select:focus,.field textarea:focus{border-color:var(--primary);box-shadow:0 0 0 3px #5645d429}.secondary-button{cursor:pointer;border:0;border:1px solid var(--sidebar-border);min-height:42px;color:var(--sidebar-text);background:0 0;border-radius:8px;font-size:14px;font-weight:500}.secondary-button:hover{background:var(--sidebar-hover)}.sidebar-brand-icon{flex-shrink:0;justify-content:center;align-items:center;margin-right:auto;display:flex}.sidebar-actions{gap:8px;margin-left:auto;display:flex}.sidebar-top-row{align-items:center;gap:8px;display:flex}.sidebar-collapse-btn{border:1px solid var(--sidebar-border);width:28px;height:28px;color:var(--sidebar-text-muted);cursor:pointer;background:0 0;border-radius:6px;justify-content:center;align-self:center;align-items:center;margin-top:auto;transition:background .12s,color .12s;display:flex}.sidebar-collapse-btn:hover{background:var(--sidebar-hover);color:var(--sidebar-text)}.sidebar-expand-btn{border:1px solid var(--hairline);background:var(--canvas);width:24px;height:48px;color:var(--ink-muted);cursor:pointer;z-index:10;border-right:none;border-radius:0 6px 6px 0;justify-content:center;align-items:center;transition:background .12s,color .12s;display:flex;position:fixed;top:50%;left:0;transform:translateY(-50%)}.sidebar-expand-btn:hover{background:var(--canvas-soft);color:var(--primary)}.icon-button{border:1px solid var(--sidebar-border);width:36px;height:36px;color:var(--sidebar-text-muted);cursor:pointer;background:0 0;border-radius:8px;justify-content:center;align-items:center;transition:background .12s,color .12s;display:flex}.icon-button:hover{background:var(--sidebar-hover);color:var(--sidebar-text)}.icon-button:disabled{cursor:not-allowed;opacity:.4}.secondary-button:disabled{cursor:not-allowed;opacity:.55}.session-manager{gap:14px;min-height:0;display:grid}.session-section-heading{color:var(--sidebar-text-muted);text-transform:uppercase;justify-content:space-between;align-items:center;font-size:12px;font-weight:700;display:flex}.session-section-heading small{color:var(--sidebar-text-muted);font-size:12px}.session-list,.archived-sessions{gap:8px;display:grid}.session-list{max-height:min(36vh,360px);padding-right:4px;overflow-y:auto}.session-row{border:1px solid var(--sidebar-border);background:var(--canvas);border-radius:8px;gap:8px;padding:10px;display:grid}.session-row-active{border-color:var(--primary);background:var(--sidebar-active)}.session-row-archived{opacity:.7}.session-select{min-width:0;color:var(--sidebar-text);cursor:pointer;text-align:left;background:0 0;border:0;padding:0;display:grid}.session-select:disabled{cursor:not-allowed}.session-select span{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:650;overflow:hidden}.session-select small{color:var(--sidebar-text-muted);font-size:12px}.session-row-actions{gap:8px;display:flex}.session-row-actions button{border:1px solid var(--sidebar-border);min-height:28px;color:var(--sidebar-text-muted);cursor:pointer;background:0 0;border-radius:8px;padding:0 9px;font-size:12px;font-weight:600}.session-row-actions button:disabled{cursor:not-allowed;opacity:.5}.session-row-actions button:not(:disabled):hover{background:var(--sidebar-hover);color:var(--sidebar-text)}.session-rename-input{border:1px solid var(--sidebar-border);background:var(--canvas);width:100%;min-height:34px;color:var(--sidebar-text);border-radius:8px;outline:none;padding:0 10px}.pi-sessions-section{flex-direction:column;flex:1;gap:8px;min-height:0;display:flex}.pi-sessions-list{flex:1;align-content:start;gap:4px;min-height:0;padding-right:4px;display:grid;overflow-y:auto}.pi-project-group{gap:2px;display:grid}.pi-project-header-row{align-items:center;gap:4px;display:flex}.pi-project-header{border:1px solid var(--sidebar-border);background:var(--canvas);min-width:0;min-height:32px;color:var(--sidebar-text);cursor:pointer;letter-spacing:-.125px;text-align:left;border-radius:8px;flex:1;align-items:center;gap:8px;padding:6px 10px;font-size:14px;font-weight:600;transition:background .12s;display:flex}.pi-project-header:disabled{cursor:not-allowed;opacity:.45}.pi-project-header:hover:not(:disabled){background:var(--sidebar-hover)}.pi-new-session-btn{border:1px solid var(--sidebar-border);width:32px;height:32px;color:var(--sidebar-text-muted);cursor:pointer;background:0 0;border-radius:8px;flex-shrink:0;justify-content:center;align-items:center;transition:background .12s,color .12s,border-color .12s;display:flex}.pi-new-session-btn:hover:not(:disabled){background:var(--sidebar-hover);border-color:var(--sidebar-border);color:var(--sidebar-text)}.pi-new-session-btn:disabled{cursor:not-allowed;opacity:.45}.pi-project-name{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.pi-project-count{background:var(--sidebar-hover);min-width:24px;color:var(--sidebar-text-muted);letter-spacing:.125px;text-align:center;border-radius:9999px;flex-shrink:0;padding:0 8px;font-size:12px;font-weight:600;line-height:22px}.pi-session-list{gap:2px;padding-left:12px;display:grid}.pi-session-row{cursor:pointer;text-align:left;background:0 0;border:0;border-radius:5px;width:100%;padding:8px 10px;transition:background .12s;display:grid}.pi-session-row:hover{background:var(--sidebar-hover)}.pi-session-row:disabled{cursor:not-allowed;opacity:.46}.pi-session-row-active{background:var(--sidebar-active);border-left:2px solid var(--primary)}.pi-project-group-drag-over{background:var(--sidebar-active);outline:1px dashed var(--primary);outline-offset:-1px;border-radius:8px}.pi-session-info{gap:3px;min-width:0;display:grid}.pi-session-first-msg{color:var(--sidebar-text);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:400;line-height:1.45;overflow:hidden}.pi-session-meta{color:var(--sidebar-text-muted);letter-spacing:0;align-items:center;gap:6px;font-size:12px;font-weight:400;display:flex}.pi-sessions-loading,.pi-sessions-error,.pi-sessions-empty{color:var(--sidebar-text-muted);padding:8px 10px;font-size:14px}.status-panel{border:1px solid var(--sidebar-border);background:var(--canvas);color:var(--sidebar-text-muted);border-radius:12px;align-items:flex-start;gap:12px;margin-top:auto;padding:16px;line-height:1.5;display:flex}.status-panel p{margin:0}.status-dot{background:var(--accent-teal);border-radius:999px;width:10px;height:10px;margin-top:7px;box-shadow:0 0 0 4px #2a9d992e}.chat-panel{grid-template-rows:auto 1fr auto;min-width:0;max-height:100vh;display:grid}.chat-header{border-bottom:1px solid var(--hairline);background:var(--canvas);justify-content:space-between;align-items:center;gap:16px;padding:10px 32px;display:flex}.chat-header-copy{gap:3px;min-width:0;display:grid}.chat-header-title{color:var(--ink-secondary);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:600;line-height:1.4;overflow:hidden}.chat-header-meta{color:var(--ink-faint);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:1.4;overflow:hidden}.chat-mode-pill{background:var(--card-tint-sky);min-height:28px;color:var(--ink-secondary);text-transform:uppercase;border-radius:999px;justify-content:center;align-items:center;padding:0 10px;font-size:12px;font-weight:700;display:inline-flex}.messages{background:var(--canvas);min-height:0;overflow:hidden}.messages-empty{flex-direction:column;gap:16px;padding:32px;display:flex;overflow-y:auto}.empty-state{border:1px dashed var(--hairline);background:var(--canvas);max-width:560px;color:var(--ink-muted);text-align:center;border-radius:12px;margin:auto;padding:32px}.empty-state h3{color:var(--ink-secondary)}.chat-bubble-list{height:100%}.chat-bubble-list .ant-bubble-list-scroll-box{height:100%;padding:32px;overflow-y:auto}.chat-bubble-list .ant-bubble-list-scroll-content{flex-direction:column;gap:16px;display:flex}.chat-bubble{max-width:760px;line-height:1.6}.chat-bubble .ant-bubble-content{border:1px solid var(--hairline);background:var(--canvas);white-space:pre-wrap;border-radius:12px;padding:16px 18px}.chat-bubble-user{margin-left:auto}.chat-bubble-user .ant-bubble-content{border-color:var(--card-tint-sky);background:var(--card-tint-sky)}.message-content{gap:12px;display:grid}.message-content p{margin:0}.chat-bubble .ant-bubble-content h1,.chat-bubble .ant-bubble-content h2,.chat-bubble .ant-bubble-content h3,.chat-bubble .ant-bubble-content h4{color:var(--ink-secondary);margin:1em 0 .5em;font-weight:600;line-height:1.3}.chat-bubble .ant-bubble-content h1{font-size:1.4em}.chat-bubble .ant-bubble-content h2{font-size:1.2em}.chat-bubble .ant-bubble-content h3{font-size:1.1em}.chat-bubble .ant-bubble-content p{margin:0 0 .75em;line-height:1.65}.chat-bubble .ant-bubble-content p:last-child{margin-bottom:0}.chat-bubble .ant-bubble-content ul,.chat-bubble .ant-bubble-content ol{margin:0 0 .75em;padding-left:1.5em;line-height:1.65}.chat-bubble .ant-bubble-content li{margin-bottom:.25em}.chat-bubble .ant-bubble-content li:last-child{margin-bottom:0}.chat-bubble .ant-bubble-content blockquote{border-left:3px solid var(--hairline);color:var(--ink-muted);margin:0 0 .75em;padding:.25em 1em}.chat-bubble .ant-bubble-content hr{border:none;border-top:1px solid var(--hairline);margin:1em 0}.chat-bubble .ant-bubble-content a{color:var(--primary);text-underline-offset:2px;text-decoration:underline}.chat-bubble .ant-bubble-content a:hover{color:var(--primary-active)}.chat-bubble .ant-bubble-content strong{font-weight:600}.chat-bubble .ant-bubble-content table{border-collapse:collapse;width:100%;margin:0 0 .75em;font-size:14px}.chat-bubble .ant-bubble-content th,.chat-bubble .ant-bubble-content td{border:1px solid var(--hairline);text-align:left;padding:8px 12px;line-height:1.5}.chat-bubble .ant-bubble-content th{background:var(--canvas-soft);font-weight:600}.message-images{flex-wrap:wrap;gap:10px;display:flex}.message-image{gap:6px;width:min(240px,100%);margin:0;display:grid}.message-image img{border:1px solid var(--hairline);object-fit:cover;border-radius:8px;width:100%;max-height:220px;display:block}.message-image figcaption{color:var(--ink-muted);text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:500;overflow:hidden}.message-meta{color:var(--ink-faint);text-transform:uppercase;justify-content:space-between;align-items:center;gap:16px;margin-bottom:10px;font-size:12px;font-weight:600;display:flex}.message-meta small{color:var(--ink-faint);text-transform:none;font-size:11px;line-height:1.4}.error-banner{max-width:760px;color:var(--semantic-error);background:#fff0ed;border:1px solid #e0313142;border-radius:8px;padding:14px 16px;font-weight:600}.error-banner p{margin:0 0 10px}.inline-action-button{min-height:32px;color:inherit;cursor:pointer;background:0 0;border:1px solid;border-radius:8px;padding:0 12px;font-size:13px;font-weight:600}.inline-action-button:hover{background:#e0313114}.pi-tool-card{border:1px solid var(--hairline);background:var(--canvas-soft);border-radius:10px;overflow:hidden}.pi-tool-card summary{cursor:pointer;color:var(--ink-secondary);justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;font-size:14px;font-weight:600;list-style:none;display:flex}.pi-tool-card summary::-webkit-details-marker{display:none}.pi-tool-card summary small{color:var(--ink-faint);font-size:12px;font-weight:600}.pi-tool-card pre{color:var(--ink-secondary);white-space:pre-wrap;word-break:break-word;margin:0;padding:0 14px 14px;font-size:12px;line-height:1.55;overflow-x:auto}.pi-tool-card-error{background:#fff4f2;border-color:#e0313142}.pi-summary-card{border:1px solid var(--hairline);background:var(--canvas-soft);border-radius:10px;gap:8px;padding:14px;display:grid}.pi-summary-card strong{color:var(--ink-secondary);letter-spacing:.02em;text-transform:uppercase;font-size:13px;font-weight:700}.pi-summary-card p{color:var(--ink-muted);margin:0;font-size:14px;line-height:1.6}.pi-summary-card-compaction{background:var(--card-tint-sky)}.pi-summary-card-branch{background:var(--card-tint-yellow)}.pi-summary-card-custom{background:var(--card-tint-mint)}.composer{border-top:1px solid var(--hairline);background:var(--canvas);padding:12px 20px}.pi-history-footer{border-top:1px solid var(--hairline);background:var(--canvas-soft);color:var(--ink-faint);align-items:center;padding:18px 32px 24px;font-size:13px;font-weight:600;display:flex}.attachment-preview{border:1px solid var(--hairline);background:var(--canvas);border-radius:8px;grid-template-columns:56px minmax(0,1fr) auto;align-items:center;gap:12px;margin-bottom:12px;padding:10px;display:grid}.attachment-preview img{object-fit:cover;border-radius:6px;width:56px;height:56px}.attachment-preview div{gap:2px;min-width:0;display:grid}.attachment-preview strong,.attachment-preview span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.attachment-preview strong{color:var(--ink);font-size:14px;font-weight:600}.attachment-preview span{color:var(--ink-faint);font-size:12px}.attachment-preview button{border:1px solid var(--hairline);background:var(--canvas);min-height:32px;color:var(--ink-secondary);cursor:pointer;border-radius:8px;padding:0 12px;font-size:13px;font-weight:600}.attachment-preview button:hover,.attachment-preview button:not(:disabled):hover{border-color:var(--primary);color:var(--primary)}.composer-upload-input{display:none}.chat-sender{border:1px solid var(--hairline);background:var(--canvas-soft);border-radius:8px}.chat-sender .ant-sender-content{grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:10px;min-height:64px;padding:10px;display:grid}.chat-sender .ant-sender-actions-list{place-self:end;margin:0;padding:0 0 2px}.chat-sender .ant-sender-input{min-width:0;font-size:14px;line-height:1.5}.chat-sender .ant-sender-actions-btn .ant-btn-primary{background:var(--primary)}.chat-sender .ant-sender-actions-btn .ant-btn-primary:not(:disabled):hover{background:var(--primary-active)}.slash-command-suggestion{width:100%}.slash-command-option{gap:2px;min-width:240px;display:grid}.slash-command-option span{color:var(--ink);font-size:14px;font-weight:600}.slash-command-option small{color:var(--ink-faint);font-size:12px;line-height:1.35}.slash-command-source{color:var(--ink-faint);text-transform:uppercase;font-size:11px;font-weight:600}.settings-modal-content{gap:18px;padding-top:4px;display:grid}@media (width<=860px){.app-shell{grid-template-columns:1fr}.sidebar{border-right:0;border-bottom:1px solid var(--sidebar-border)}.session-list{max-height:280px}.chat-panel{max-height:none}}@media (width<=560px){.sidebar,.messages-empty,.chat-bubble-list .ant-bubble-list-scroll-box,.composer{padding-left:18px;padding-right:18px}.attachment-preview{grid-template-columns:48px minmax(0,1fr)}.attachment-preview button{grid-column:1/-1;width:100%}.chat-header{padding:8px 18px}}
|