paseo-terminal-integration 0.2.0 → 0.3.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/README.md CHANGED
@@ -6,27 +6,26 @@ Two-way bridge between agent chats and Paseo terminals.
6
6
  (`bash`, `sh`, `shell`, `console`, `zsh`, `fish`, `python`, `js`/`node`) get a **Run in terminal**
7
7
  card under the reply:
8
8
 
9
- - **Run in side terminal** runs the block in the workspace terminal "Agent commands" and shows it in
10
- the side panel; **Run silently** runs it without showing anything.
11
- - With **Send to agent** checked (the default), the command, exit code, and output go to the agent
12
- as a new message when the block finishes. If the agent is busy, delivery waits until its turn
13
- ends instead of interrupting it. Unchecked, **Send output** sends it later.
9
+ - **Run** runs the block in the workspace terminal "Agent commands", a regular Paseo terminal tab.
10
+ - **Run in background** runs it as a hidden process in the agent's working directory: no terminal
11
+ tab, and the card keeps the last 5,000 lines instead of what fits on a terminal screen. Nothing
12
+ can be typed into it: stdin is empty, so password and confirmation prompts fail instead of
13
+ waiting. Background runs do not wait for the terminal.
14
+ - With **Send output to agent** checked (the default), the command, exit code, and output go to
15
+ the agent as a new message when the block finishes. If the agent is busy, delivery waits until
16
+ its turn ends instead of interrupting it. Unchecked, **Send output** sends it later.
14
17
  - **Stop** sends Ctrl-C; **Send output so far** reports a still-running command.
15
18
 
16
- **Terminal panel.** The side panel (Explorer) shows "Agent commands" as a full terminal: the same
17
- xterm.js renderer and live stream as Paseo's terminal tabs, with colors, the cursor, full-screen
18
- programs, and keyboard input. Click it and type to answer `sudo` passwords, `y/n` questions, or
19
- npm's "Press ENTER" without leaving the chat. Ctrl-C copies while text is selected and otherwise
20
- goes to the shell; Ctrl-Shift-C and Ctrl-Shift-V always copy and paste. Web links open in the
21
- browser when clicked. The card's **Terminal** button opens the panel without running anything. On
22
- phones the panel is a plain-text copy with an input line and Enter, Ctrl-C, Ctrl-D, and Tab keys,
23
- in a tab that opens only when you ask. **Settings → Plugins → Terminal integration** switches the
24
- panel to a workspace tab and sets the checkbox's default.
25
-
26
- Paseo gives every workspace terminal its own tab, so "Agent commands" also appears in the tab bar
27
- (in the background). Closing that tab ends the terminal; the next run starts a new one. Runs in
28
- one workspace are queued so a command never types into another one's stdin. Each block runs as a
29
- script in the agent's working directory, so `cd` and `export` do not persist between runs.
19
+ **Terminal beside the chat.** "Agent commands" opens as a background tab. Drag the tab onto the
20
+ right or bottom edge of the chat to split the workspace; it stays there while the terminal is open,
21
+ so you can watch runs and type answers to `sudo` passwords or `y/n` questions next to the
22
+ conversation. From the keyboard, split the pane and move the tab into it with the shortcuts in
23
+ **Settings → Shortcuts → Tabs & panes**. Closing the tab ends the terminal; the next run starts a
24
+ new one in a new tab.
25
+
26
+ Terminal runs in one workspace are queued so a command never types into another one's stdin.
27
+ Each block runs as a script in the agent's working directory, so `cd` and `export` do not persist
28
+ between runs. **Settings → Plugins → Terminal integration** sets the checkbox's default.
30
29
 
31
30
  **Terminal → agent.** In the composer's attachment menu, **Terminal output** attaches the latest
32
31
  lines of any terminal on the daemon. Type part of a name or path to filter, and a number to choose
@@ -39,10 +38,10 @@ Requires Paseo 0.10.1 or later and a macOS or Linux daemon host with `bash` (plu
39
38
 
40
39
  ```bash
41
40
  paseo plugin install npm:paseo-terminal-integration
41
+ # or from GitHub
42
+ paseo plugin install github:keefeere/paseo-terminal-integration
42
43
  ```
43
44
 
44
- Install from npm: GitHub installs skip npm dependencies, which the terminal panel needs.
45
-
46
45
  Plugins are trusted, unsandboxed code: this one types commands into terminals on the daemon host.
47
46
  Nothing runs until you press a button or submit `/run`.
48
47
 
@@ -53,17 +52,13 @@ Nothing runs until you press a button or submit `/run`.
53
52
  | `/run <command>` | Run a shell command and send its output to the agent |
54
53
  | `/blocks` | Add a run card for the latest reply (e.g. older chats) |
55
54
  | ⌘K / Ctrl+K → "Terminal: add run buttons to …" | Same as `/blocks` |
56
- | ⌘K / Ctrl+K → "Terminal: show … in the side panel" | Open the terminal panel in the Explorer |
57
55
 
58
56
  ## Limitations
59
57
 
60
- - Output is read from the terminal screen, so very long lines arrive wrapped and only the last
58
+ - Terminal output is read from the screen, so very long lines arrive wrapped and only the last
61
59
  3,000 rows are visible. The agent receives at most 300 lines or 24,000 characters.
62
60
  - Run state lives in the plugin process; reloading the plugin forgets it (the cards stay).
63
- - The live panel opens its own connection to the daemon on the plugin host, using the address in
64
- `$PASEO_HOME/paseo.pid` and the local credential, as the `paseo` CLI does. A daemon listening on
65
- a Unix socket gets the plain-text panel instead.
66
- - Opening or resizing the panel resizes the PTY to fit it, as focusing a terminal tab does.
61
+ - Plugins cannot split panes or move tabs, so placing the terminal beside the chat is up to you.
67
62
  - A command that has not started within 20 seconds fails, for example when something else is
68
63
  running in "Agent commands".
69
64
 
@@ -77,6 +72,14 @@ paseo plugin reload terminal-integration
77
72
  paseo plugin logs terminal-integration
78
73
  ```
79
74
 
75
+ To release, bump the version and push the tag; GitHub Actions publishes it to npm through trusted
76
+ publishing, and Paseo Cafe picks up the new version from there:
77
+
78
+ ```bash
79
+ npm version patch # or minor; commits and tags vX.Y.Z
80
+ git push --follow-tags
81
+ ```
82
+
80
83
  ## License
81
84
 
82
85
  MIT
@@ -1,4 +1,4 @@
1
- import { useAgent, useRpc, useSettings, type PluginTimelineItemProps } from "@getpaseo/plugin/client";
1
+ import { useRpc, useSettings, type PluginTimelineItemProps } from "@getpaseo/plugin/client";
2
2
  import { copyText, Icon, useToast } from "@getpaseo/plugin/client/react-native";
3
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useMemo, useState } from "react";
@@ -11,10 +11,10 @@ import {
11
11
  startRun,
12
12
  TERMINAL_NAME,
13
13
  type RunCardData,
14
+ type RunMode,
14
15
  type RunSnapshot,
15
16
  } from "../shared/contracts";
16
17
  import { preferences } from "../shared/settings";
17
- import { openTerminalPanel } from "./panel-opener";
18
18
 
19
19
  type Theme = PluginTimelineItemProps["theme"];
20
20
  type Styles = ReturnType<typeof createStyles>;
@@ -44,24 +44,15 @@ export function RunCard({ item, agentId, theme, layout }: PluginTimelineItemProp
44
44
  const cancel = useRpc(cancelRun);
45
45
  const send = useRpc(sendRunOutput);
46
46
 
47
- const workspaceId = useAgent(agentId, (agent) => agent.workspaceId);
48
47
  const settings = useSettings(preferences);
49
- const saved = settings.status === "ready" ? settings.values : null;
50
- const panelLocation = saved?.panelLocation ?? "explorer";
51
48
  const [sendChoice, setSendChoice] = useState<boolean | null>(null);
52
- const sendToAgent = sendChoice ?? saved?.sendToAgent ?? true;
53
- const runVisibleLabel =
54
- layout.compact || panelLocation === "workspace" ? "Run in terminal tab" : "Run in side terminal";
49
+ const sendToAgent =
50
+ sendChoice ?? (settings.status === "ready" ? settings.values.sendToAgent : true);
55
51
 
56
- function showTerminal() {
57
- if (!workspaceId) return;
58
- openTerminalPanel(workspaceId, panelLocation, { compact: layout.compact, explicit: true });
59
- }
60
- function runBlock(key: string, block: RunCardData["blocks"][number], visible: boolean) {
61
- return act(key, async () => {
62
- await start({ agentId, key, lang: block.lang, code: block.code, send: sendToAgent });
63
- if (visible) showTerminal();
64
- });
52
+ function runBlock(key: string, block: RunCardData["blocks"][number], mode: RunMode) {
53
+ return act(key, () =>
54
+ start({ agentId, key, lang: block.lang, code: block.code, send: sendToAgent, mode }),
55
+ );
65
56
  }
66
57
 
67
58
  const queryKey = useMemo(() => ["terminal-run-status", cardId], [cardId]);
@@ -99,19 +90,10 @@ export function RunCard({ item, agentId, theme, layout }: PluginTimelineItemProp
99
90
  <Checkbox
100
91
  styles={styles}
101
92
  theme={theme}
102
- label="Send to agent"
93
+ label="Send output to agent"
103
94
  checked={sendToAgent}
104
95
  onChange={setSendChoice}
105
96
  />
106
- {workspaceId ? (
107
- <ActionButton
108
- styles={styles}
109
- theme={theme}
110
- icon="PanelRight"
111
- label="Terminal"
112
- onPress={showTerminal}
113
- />
114
- ) : null}
115
97
  </View>
116
98
  {blocks.map((block, index) => {
117
99
  const key = keys[index];
@@ -154,19 +136,19 @@ export function RunCard({ item, agentId, theme, layout }: PluginTimelineItemProp
154
136
  <ActionButton
155
137
  styles={styles}
156
138
  theme={theme}
157
- icon="PanelRight"
158
- label={runVisibleLabel}
139
+ icon="Play"
140
+ label="Run"
159
141
  tone="primary"
160
142
  disabled={busy || active}
161
- onPress={() => runBlock(key, block, true)}
143
+ onPress={() => runBlock(key, block, "terminal")}
162
144
  />
163
145
  <ActionButton
164
146
  styles={styles}
165
147
  theme={theme}
166
- icon="Play"
167
- label="Run silently"
148
+ icon="EyeOff"
149
+ label="Run in background"
168
150
  disabled={busy || active}
169
- onPress={() => runBlock(key, block, false)}
151
+ onPress={() => runBlock(key, block, "background")}
170
152
  />
171
153
  {run && !run.sent && !active ? (
172
154
  <ActionButton
@@ -205,7 +187,9 @@ function RunStatus({ run, styles }: { run: RunSnapshot; styles: Styles }) {
205
187
  const parts: string[] = [];
206
188
  let tone: TextStyle = styles.muted;
207
189
  if (run.status === "queued") parts.push("Queued, waiting for the terminal");
208
- if (run.status === "running") parts.push(`Running · ${elapsed(run)}`);
190
+ if (run.status === "running") {
191
+ parts.push(`${run.mode === "background" ? "Running in background" : "Running"} · ${elapsed(run)}`);
192
+ }
209
193
  if (run.status === "done") {
210
194
  parts.push(`${run.exitCode === 0 ? "✓" : "✗"} exit ${run.exitCode}`);
211
195
  tone = run.exitCode === 0 ? styles.success : styles.danger;
@@ -3,16 +3,11 @@ import {
3
3
  SettingsCard,
4
4
  SettingsRow,
5
5
  SettingsSection,
6
- SettingsSelect,
7
6
  SettingsSwitch,
8
7
  } from "@getpaseo/plugin/client/ui";
9
8
  import { Text } from "react-native";
10
- import { preferences, type PanelLocation } from "../shared/settings";
11
-
12
- const panelLocationChoices: { label: string; value: PanelLocation }[] = [
13
- { label: "Side panel (Explorer)", value: "explorer" },
14
- { label: "Workspace tab", value: "workspace" },
15
- ];
9
+ import { TERMINAL_NAME } from "../shared/contracts";
10
+ import { preferences } from "../shared/settings";
16
11
 
17
12
  export function SettingsScreen({ theme }: PluginSurfaceProps) {
18
13
  const settings = useSettings(preferences);
@@ -27,25 +22,17 @@ export function SettingsScreen({ theme }: PluginSurfaceProps) {
27
22
  return (
28
23
  <SettingsSection title="Run cards">
29
24
  <SettingsCard>
30
- <SettingsSelect
31
- label="Show the terminal in"
32
- hint={`Used by "Run in side terminal", the Terminal button, and /run. Phones always use a tab and never switch to it automatically.`}
33
- value={values.panelLocation}
34
- options={panelLocationChoices}
35
- disabled={settings.saving}
36
- error={settings.saveError}
37
- onValueChange={(panelLocation) => void settings.save({ ...values, panelLocation }, revision)}
38
- />
39
25
  <SettingsSwitch
40
26
  label="Send output to the agent by default"
41
- hint={`Initial state of the "Send to agent" checkbox on each run card.`}
27
+ hint={`Initial state of the "Send output to agent" checkbox on each run card.`}
42
28
  value={values.sendToAgent}
43
29
  disabled={settings.saving}
30
+ error={settings.saveError}
44
31
  onValueChange={(sendToAgent) => void settings.save({ ...values, sendToAgent }, revision)}
45
32
  />
46
33
  <SettingsRow
47
- label="What the panel shows"
48
- hint={`The live "Agent commands" terminal with colors and keyboard input, for prompts such as sudo passwords, y/n questions, or 2FA codes. Ctrl-C copies while text is selected; Ctrl-Shift-C and Ctrl-Shift-V copy and paste. Phones show a plain-text copy with an input line.`}
34
+ label="Where the terminal appears"
35
+ hint={`Commands run in the "${TERMINAL_NAME}" terminal tab. To keep it beside the chat, drag the tab onto the right or bottom edge of the chat; it stays there while the terminal is open.`}
49
36
  />
50
37
  </SettingsCard>
51
38
  </SettingsSection>
package/index.client.tsx CHANGED
@@ -1,22 +1,18 @@
1
+ import type { PluginCleanup } from "@getpaseo/plugin";
1
2
  import type { PluginClientContext } from "@getpaseo/plugin/client";
2
- import { setPanelOpener } from "./client/panel-opener";
3
3
  import { RunCard } from "./client/run-card";
4
4
  import { SettingsScreen } from "./client/settings-screen";
5
- import { TerminalPanel } from "./client/terminal-panel";
6
5
  import {
7
6
  RUN_CARD_KIND,
8
7
  RUN_CARD_VERSION,
9
8
  runAdhoc,
10
9
  runCardSchema,
11
10
  scanBlocks,
12
- TERMINAL_NAME,
13
11
  terminalOutputSource,
14
12
  } from "./shared/contracts";
15
- import { TERMINAL_PANEL_ID } from "./shared/settings";
16
-
17
- export default function contribute(client: PluginClientContext) {
18
- setPanelOpener((id, options) => client.openPanel(id, options));
19
13
 
14
+ // A function declaration: the host reads the default export before the module body has run.
15
+ export default function contribute(client: PluginClientContext): PluginCleanup {
20
16
  client.addTimelineRenderer({
21
17
  kind: RUN_CARD_KIND,
22
18
  version: RUN_CARD_VERSION,
@@ -24,15 +20,6 @@ export default function contribute(client: PluginClientContext) {
24
20
  Component: RunCard,
25
21
  });
26
22
 
27
- client.addWorkspacePanel({
28
- id: TERMINAL_PANEL_ID,
29
- title: TERMINAL_NAME,
30
- icon: "SquareTerminal",
31
- context: "workspace",
32
- locations: ["explorer", "workspace"],
33
- Component: TerminalPanel,
34
- });
35
-
36
23
  client.addSettingsScreen({
37
24
  id: "preferences",
38
25
  title: "Terminal integration",
@@ -47,14 +34,9 @@ export default function contribute(client: PluginClientContext) {
47
34
  description: "Run a shell command in the workspace terminal and send its output to the agent",
48
35
  argumentHint: "<command>",
49
36
  context: "agent",
50
- async onSubmit({ args, agent, rpc, openPanel }) {
37
+ async onSubmit({ args, agent, rpc }) {
51
38
  if (!args) throw new Error("Usage: /run <command>");
52
- const { panelLocation } = await rpc(runAdhoc, { agentId: agent.id, command: args });
53
- try {
54
- openPanel(TERMINAL_PANEL_ID, { location: panelLocation });
55
- } catch {
56
- // Compact layouts have no Explorer; the card's Terminal button still opens a tab.
57
- }
39
+ await rpc(runAdhoc, { agentId: agent.id, command: args });
58
40
  },
59
41
  });
60
42
 
@@ -80,20 +62,5 @@ export default function contribute(client: PluginClientContext) {
80
62
  onSelect: ({ agent, rpc }) => addRunButtons(agent.id, rpc),
81
63
  });
82
64
 
83
- client.addCommandCenterItem({
84
- id: "open-terminal-panel",
85
- title: `Terminal: show "${TERMINAL_NAME}" in the side panel`,
86
- icon: "PanelRight",
87
- keywords: ["terminal", "explorer", "sidebar", "agent commands"],
88
- context: "workspace",
89
- onSelect({ openPanel }) {
90
- try {
91
- openPanel(TERMINAL_PANEL_ID, { location: "explorer" });
92
- } catch {
93
- openPanel(TERMINAL_PANEL_ID, { location: "workspace" });
94
- }
95
- },
96
- });
97
-
98
- return () => setPanelOpener(null);
65
+ return () => {};
99
66
  }
package/index.server.ts CHANGED
@@ -2,14 +2,9 @@ import type { PluginServerContext } from "@getpaseo/plugin/server";
2
2
  import { searchTerminals } from "./server/attachments";
3
3
  import { appendCardForTurn, appendRunCard, scanLatestReply } from "./server/cards";
4
4
  import { cleanupScripts, TerminalRunner } from "./server/runner";
5
- import { TerminalStreams } from "./server/streams";
6
5
  import { preferences } from "./shared/settings";
7
6
  import {
8
7
  cancelRun,
9
- closeTerminalView,
10
- openTerminalView,
11
- readTerminalView,
12
- resizeTerminalView,
13
8
  runAdhoc,
14
9
  runKey,
15
10
  runStatus,
@@ -20,9 +15,8 @@ import {
20
15
  } from "./shared/contracts";
21
16
 
22
17
  export default function contribute(server: PluginServerContext) {
23
- const settings = server.registerSettings(preferences);
18
+ server.registerSettings(preferences);
24
19
  let runner: TerminalRunner | null = null;
25
- const streams = new TerminalStreams();
26
20
  const runnerFor = (paseo: ConstructorParameters<typeof TerminalRunner>[0]) =>
27
21
  (runner ??= new TerminalRunner(paseo));
28
22
 
@@ -37,9 +31,14 @@ export default function contribute(server: PluginServerContext) {
37
31
  const block = { lang: "bash", code: command };
38
32
  const cardId = await appendRunCard(paseo, agentId, [block]);
39
33
  if (!cardId) throw new Error("Command is too long");
40
- await runnerFor(paseo).start({ agentId, key: runKey(cardId, 0), ...block, send: true });
41
- const saved = await settings.read();
42
- return { cardId, panelLocation: saved.status === "ready" ? saved.values.panelLocation : "explorer" };
34
+ await runnerFor(paseo).start({
35
+ agentId,
36
+ key: runKey(cardId, 0),
37
+ ...block,
38
+ send: true,
39
+ mode: "terminal",
40
+ });
41
+ return { cardId };
43
42
  });
44
43
 
45
44
  server.handle(scanBlocks, async ({ agentId }, { paseo }) => ({
@@ -48,14 +47,8 @@ export default function contribute(server: PluginServerContext) {
48
47
 
49
48
  server.handle(searchTerminalOutput, searchTerminals);
50
49
 
51
- server.handle(openTerminalView, (input) => streams.open(input));
52
- server.handle(readTerminalView, (input) => streams.read(input));
53
- server.handle(resizeTerminalView, (input) => streams.resize(input));
54
- server.handle(closeTerminalView, (input) => streams.close(input));
55
-
56
50
  return async () => {
57
51
  runner?.stop();
58
- await streams.stop();
59
52
  await cleanupScripts();
60
53
  };
61
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paseo-terminal-integration",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "files": [
5
5
  "paseo-plugin.json",
6
6
  "index.client.tsx",
@@ -38,11 +38,5 @@
38
38
  "paseo-plugin",
39
39
  "terminal",
40
40
  "agents"
41
- ],
42
- "dependencies": {
43
- "@getpaseo/client": "0.10.1",
44
- "@xterm/addon-fit": "^0.11.0",
45
- "@xterm/addon-web-links": "^0.12.0",
46
- "@xterm/xterm": "^6.0.0"
47
- }
41
+ ]
48
42
  }