@marimo-team/islands 0.25.3-dev5 → 0.25.3-dev6
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/dist/{ErrorBoundary-BiZWOrv4.js → ErrorBoundary-Ca-Sa492.js} +1 -1
- package/dist/{code-visibility-CW5_8ZOI.js → code-visibility-NGRjhbtA.js} +1 -1
- package/dist/{common-mzWw4FdI.js → common-B7_ZSg0P.js} +4755 -4744
- package/dist/{data-grid-overlay-editor-CJmehSdc.js → data-grid-overlay-editor-B_cmrkBm.js} +1 -1
- package/dist/{feedback-button-BcT1FBud.js → feedback-button-CTF2Nj9s.js} +2 -2
- package/dist/{glide-data-editor-XckatD0j.js → glide-data-editor-BQHF9puC.js} +3 -3
- package/dist/{globals-jRrLCkrP.js → globals-Cj8iqMEp.js} +1 -1
- package/dist/main.js +6 -6
- package/dist/{number-overlay-editor-pZx8dUDV.js → number-overlay-editor-DBcnm0SY.js} +1 -1
- package/dist/{reveal-component-79ez8-D5.js → reveal-component-DTBSuGkC.js} +2 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/mount.test.ts +8 -8
- package/src/components/editor/Output.tsx +5 -1
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +267 -62
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +196 -41
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +127 -13
- package/src/components/editor/errors/send-error-report-button.tsx +10 -7
- package/src/components/editor/output/MarimoTracebackOutput.tsx +27 -21
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/error-report.test.ts +53 -3
- package/src/core/cells/__tests__/session.test.ts +18 -3
- package/src/core/cells/error-report.ts +14 -6
- package/src/core/cells/session.ts +19 -1
- package/src/core/config/pair.ts +5 -3
- package/src/mount.tsx +3 -3
package/package.json
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { pairConfigSchema } from "@/core/config/pair";
|
|
6
6
|
|
|
7
7
|
// Read the Python-owned resource only in tests to check the shared contract.
|
|
8
|
-
export const
|
|
8
|
+
export const PAIR_CONFIG = pairConfigSchema.parse({
|
|
9
9
|
command: "uvx marimo@latest",
|
|
10
|
+
hostEnabled: true,
|
|
10
11
|
templates: JSON.parse(
|
|
11
12
|
readFileSync(
|
|
12
13
|
path.resolve(
|
|
@@ -8,8 +8,8 @@ import { connectionAtom } from "@/core/network/connection";
|
|
|
8
8
|
import { store } from "@/core/state/jotai";
|
|
9
9
|
import { isStaticNotebook } from "@/core/static/static-state";
|
|
10
10
|
import { ConnectionState } from "@/core/websocket/types";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { pairConfigAtom } from "@/core/config/pair";
|
|
12
|
+
import { PAIR_CONFIG } from "./fixtures/pair-config";
|
|
13
13
|
import { mount, visibleForTesting } from "../mount";
|
|
14
14
|
|
|
15
15
|
// Mock React DOM
|
|
@@ -84,20 +84,20 @@ describe("mount", () => {
|
|
|
84
84
|
mockElement,
|
|
85
85
|
);
|
|
86
86
|
|
|
87
|
-
it("hydrates the server's Pair
|
|
88
|
-
const error = mountRead({
|
|
87
|
+
it("hydrates the server's Pair configuration", () => {
|
|
88
|
+
const error = mountRead({ pairConfig: PAIR_CONFIG });
|
|
89
89
|
|
|
90
90
|
expect(error).toBeUndefined();
|
|
91
|
-
expect(store.get(
|
|
91
|
+
expect(store.get(pairConfigAtom)).toEqual(PAIR_CONFIG);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
it("
|
|
95
|
-
store.set(
|
|
94
|
+
it("clears Pair configuration when the server omits it", () => {
|
|
95
|
+
store.set(pairConfigAtom, PAIR_CONFIG);
|
|
96
96
|
|
|
97
97
|
const error = mountRead();
|
|
98
98
|
|
|
99
99
|
expect(error).toBeUndefined();
|
|
100
|
-
expect(store.get(
|
|
100
|
+
expect(store.get(pairConfigAtom)).toBeUndefined();
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
describe("connection state initialization", () => {
|
|
@@ -66,7 +66,10 @@ export type OnRefactorWithAI = (opts: {
|
|
|
66
66
|
* Renders an output based on an OutputMessage.
|
|
67
67
|
*/
|
|
68
68
|
export const OutputRenderer: React.FC<{
|
|
69
|
-
message: Pick<
|
|
69
|
+
message: Pick<
|
|
70
|
+
OutputMessage,
|
|
71
|
+
"channel" | "data" | "mimetype" | "code" | "traceback_format"
|
|
72
|
+
>;
|
|
70
73
|
cellId?: CellId;
|
|
71
74
|
onRefactorWithAI?: OnRefactorWithAI;
|
|
72
75
|
wrapText?: boolean;
|
|
@@ -185,6 +188,7 @@ export const OutputRenderer: React.FC<{
|
|
|
185
188
|
<MarimoTracebackOutput
|
|
186
189
|
onRefactorWithAI={onRefactorWithAI}
|
|
187
190
|
traceback={data}
|
|
191
|
+
tracebackFormat={message.traceback_format}
|
|
188
192
|
code={message.code}
|
|
189
193
|
cellId={cellId}
|
|
190
194
|
/>
|
|
@@ -1,50 +1,34 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { PAIR_CONFIG } from "@/__tests__/fixtures/pair-config";
|
|
5
|
+
import { shellQuote } from "@/utils/shell";
|
|
4
6
|
import {
|
|
5
7
|
type ConnectionInfo,
|
|
6
|
-
getFileFromURL,
|
|
7
8
|
getMarimoCommand,
|
|
8
9
|
getRawPrompt,
|
|
9
10
|
getTerminalCommand,
|
|
10
11
|
maskToken,
|
|
11
12
|
} from "../pair-with-agent-commands";
|
|
12
|
-
import { shellQuote } from "@/utils/shell";
|
|
13
|
-
import { PAIR_PREVIEW } from "@/__tests__/fixtures/pair-preview";
|
|
14
13
|
|
|
15
14
|
const CONNECTION: ConnectionInfo = {
|
|
16
15
|
url: "http://localhost:8000",
|
|
17
|
-
|
|
18
|
-
session: "s_ab12cd",
|
|
16
|
+
notebook: "nb-one",
|
|
19
17
|
};
|
|
20
|
-
|
|
21
|
-
const CONNECTION_WITHOUT_FILE: ConnectionInfo = {
|
|
18
|
+
const CONNECTION_WITHOUT_TARGET: ConnectionInfo = {
|
|
22
19
|
url: "http://localhost:8000",
|
|
23
20
|
};
|
|
24
21
|
|
|
25
22
|
describe("shellQuote", () => {
|
|
26
|
-
it("quotes
|
|
23
|
+
it("quotes empty and unsafe arguments", () => {
|
|
27
24
|
expect(shellQuote("")).toBe("''");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("leaves shell-safe values untouched", () => {
|
|
31
25
|
expect(shellQuote("http://localhost:8000")).toBe("http://localhost:8000");
|
|
32
|
-
expect(shellQuote("notebooks/example.py")).toBe("notebooks/example.py");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("quotes values with shell metacharacters", () => {
|
|
36
26
|
expect(shellQuote("http://host:8000?a=1&b=2")).toBe(
|
|
37
27
|
"'http://host:8000?a=1&b=2'",
|
|
38
28
|
);
|
|
39
|
-
expect(shellQuote("has space")).toBe("'has space'");
|
|
40
29
|
expect(shellQuote("$(rm -rf /)")).toBe("'$(rm -rf /)'");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("escapes embedded single quotes without breaking out", () => {
|
|
44
|
-
// Closes the quote, emits a literal ' via "'", then reopens: '"'"'
|
|
45
30
|
expect(shellQuote("a'b")).toBe(`'a'"'"'b'`);
|
|
46
31
|
});
|
|
47
|
-
|
|
48
32
|
it.each([
|
|
49
33
|
["/tmp/my notebook.py", "'/tmp/my notebook.py'"],
|
|
50
34
|
[
|
|
@@ -55,267 +39,115 @@ describe("shellQuote", () => {
|
|
|
55
39
|
String.raw`\\server\share\my notebook.py`,
|
|
56
40
|
String.raw`'\\server\share\my notebook.py'`,
|
|
57
41
|
],
|
|
58
|
-
])("quotes
|
|
42
|
+
])("quotes path %s as one argument", (path, expected) => {
|
|
59
43
|
expect(shellQuote(path)).toBe(expected);
|
|
60
44
|
});
|
|
61
45
|
});
|
|
62
46
|
|
|
63
|
-
describe("getFileFromURL", () => {
|
|
64
|
-
it("returns undefined when the file query parameter is absent or empty", () => {
|
|
65
|
-
expect(getFileFromURL("http://localhost:8000")).toBeUndefined();
|
|
66
|
-
expect(getFileFromURL("http://localhost:8000?file=")).toBeUndefined();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("decodes spaces and literal plus signs", () => {
|
|
70
|
-
expect(
|
|
71
|
-
getFileFromURL(
|
|
72
|
-
"http://localhost:8000?file=relative%2Fmy%20notebook%2Bdata.py",
|
|
73
|
-
),
|
|
74
|
-
).toBe("relative/my notebook+data.py");
|
|
75
|
-
expect(getFileFromURL("http://localhost:8000?file=my+notebook.py")).toBe(
|
|
76
|
-
"my notebook.py",
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("preserves decoded Windows paths and uses the first duplicate value", () => {
|
|
81
|
-
expect(
|
|
82
|
-
getFileFromURL(
|
|
83
|
-
"http://localhost:8000?file=C%3A%5CUsers%5CJane%20Doe%5Cnotebook.py",
|
|
84
|
-
),
|
|
85
|
-
).toBe(String.raw`C:\Users\Jane Doe\notebook.py`);
|
|
86
|
-
expect(
|
|
87
|
-
getFileFromURL("http://localhost:8000?file=first.py&file=second.py"),
|
|
88
|
-
).toBe("first.py");
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
47
|
describe("getMarimoCommand", () => {
|
|
93
|
-
afterEach(() =>
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("uses the local checkout in dev", () => {
|
|
48
|
+
afterEach(() => vi.unstubAllEnvs());
|
|
49
|
+
it("uses the checkout command in development", () => {
|
|
98
50
|
vi.stubEnv("DEV", true);
|
|
99
51
|
expect(getMarimoCommand()).toBe("uv run marimo");
|
|
100
52
|
});
|
|
101
|
-
|
|
102
|
-
it("uses uvx outside of dev", () => {
|
|
53
|
+
it("uses uvx outside development", () => {
|
|
103
54
|
vi.stubEnv("DEV", false);
|
|
104
55
|
expect(getMarimoCommand()).toBe("uvx marimo@latest");
|
|
105
56
|
});
|
|
106
57
|
});
|
|
107
58
|
|
|
108
59
|
describe("getTerminalCommand", () => {
|
|
109
|
-
it("uses the preview launcher in production and quotes session-only targets", () => {
|
|
110
|
-
vi.stubEnv("DEV", false);
|
|
111
|
-
try {
|
|
112
|
-
expect(
|
|
113
|
-
getTerminalCommand(
|
|
114
|
-
"claude",
|
|
115
|
-
{ url: "http://host:8000?a=1&b=2", session: "s_' {file}" },
|
|
116
|
-
false,
|
|
117
|
-
{ ...PAIR_PREVIEW, command: "uvx marimo@latest" },
|
|
118
|
-
),
|
|
119
|
-
).toBe(
|
|
120
|
-
String.raw`claude "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
|
|
121
|
-
--url 'http://host:8000?a=1&b=2' \
|
|
122
|
-
--session 's_'"'"' {file}')"`,
|
|
123
|
-
);
|
|
124
|
-
expect(
|
|
125
|
-
getTerminalCommand(
|
|
126
|
-
"codex",
|
|
127
|
-
CONNECTION_WITHOUT_FILE,
|
|
128
|
-
false,
|
|
129
|
-
PAIR_PREVIEW,
|
|
130
|
-
),
|
|
131
|
-
).toBe(
|
|
132
|
-
String.raw`codex "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
|
|
133
|
-
--url http://localhost:8000)"`,
|
|
134
|
-
);
|
|
135
|
-
} finally {
|
|
136
|
-
vi.unstubAllEnvs();
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
60
|
it.each([
|
|
141
61
|
["claude", "claude"],
|
|
142
62
|
["codex", "codex"],
|
|
143
63
|
["opencode", "opencode --prompt"],
|
|
144
|
-
] as const)(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
expect(
|
|
148
|
-
getTerminalCommand(
|
|
149
|
-
agent,
|
|
150
|
-
{ ...CONNECTION, session: "s_ab12cd" },
|
|
151
|
-
true,
|
|
152
|
-
PAIR_PREVIEW,
|
|
153
|
-
),
|
|
154
|
-
).toBe(
|
|
155
|
-
String.raw`${cli} "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
|
|
156
|
-
--url http://localhost:8000 \
|
|
157
|
-
--session s_ab12cd \
|
|
158
|
-
--with-token)"`,
|
|
159
|
-
);
|
|
160
|
-
},
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
it("includes the url and agent flag for each agent", () => {
|
|
164
|
-
expect(getTerminalCommand("claude", CONNECTION, false)).toBe(
|
|
165
|
-
`claude "$(uv run marimo pair prompt --url http://localhost:8000 --claude)"`,
|
|
166
|
-
);
|
|
167
|
-
expect(getTerminalCommand("codex", CONNECTION, false)).toBe(
|
|
168
|
-
`codex "$(uv run marimo pair prompt --url http://localhost:8000 --codex)"`,
|
|
169
|
-
);
|
|
170
|
-
expect(getTerminalCommand("opencode", CONNECTION, false)).toBe(
|
|
171
|
-
`opencode --prompt "$(uv run marimo pair prompt --url http://localhost:8000 --opencode)"`,
|
|
64
|
+
] as const)("launches %s through the public CLI", (agent, cli) => {
|
|
65
|
+
expect(getTerminalCommand(agent, CONNECTION, true, PAIR_CONFIG)).toBe(
|
|
66
|
+
`${cli} "$(uvx marimo@latest pair prompt \\\n --url http://localhost:8000 \\\n --notebook nb-one \\\n --with-token)"`,
|
|
172
67
|
);
|
|
173
68
|
});
|
|
174
|
-
|
|
175
|
-
it("never passes a file to marimo pair prompt", () => {
|
|
176
|
-
const command = getTerminalCommand("claude", CONNECTION, false);
|
|
177
|
-
expect(command).not.toContain("--file");
|
|
178
|
-
expect(command).not.toContain("notebooks/example.py");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("omits the session outside the preview", () => {
|
|
182
|
-
expect(getTerminalCommand("claude", CONNECTION, false)).not.toContain(
|
|
183
|
-
"--session",
|
|
184
|
-
);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("omits the session when it is unknown", () => {
|
|
188
|
-
expect(
|
|
189
|
-
getTerminalCommand(
|
|
190
|
-
"claude",
|
|
191
|
-
CONNECTION_WITHOUT_FILE,
|
|
192
|
-
false,
|
|
193
|
-
PAIR_PREVIEW,
|
|
194
|
-
),
|
|
195
|
-
).not.toContain("--session");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("shell-escapes a url containing metacharacters", () => {
|
|
69
|
+
it("uses a stable Session ID for editor execution", () => {
|
|
199
70
|
const command = getTerminalCommand(
|
|
200
71
|
"claude",
|
|
201
|
-
{ url: "http://host:8000?
|
|
72
|
+
{ url: "http://host:8000?a=1&b=2", session: "stable_' {file}" },
|
|
202
73
|
false,
|
|
74
|
+
PAIR_CONFIG,
|
|
203
75
|
);
|
|
204
|
-
expect(command).toContain("--url 'http://host:8000?
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
it("adds --with-token before the agent flag when requested", () => {
|
|
208
|
-
const command = getTerminalCommand("claude", CONNECTION, true);
|
|
209
|
-
expect(command).toContain("--with-token --claude");
|
|
76
|
+
expect(command).toContain("--url 'http://host:8000?a=1&b=2'");
|
|
77
|
+
expect(command).toContain(`--session 'stable_'"'"' {file}'`);
|
|
78
|
+
expect(command).not.toContain("--notebook");
|
|
210
79
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
"
|
|
80
|
+
it("prefers the verified Notebook ID to a Session ID", () => {
|
|
81
|
+
const command = getTerminalCommand(
|
|
82
|
+
"codex",
|
|
83
|
+
{ ...CONNECTION, session: "stable-one" },
|
|
84
|
+
false,
|
|
85
|
+
PAIR_CONFIG,
|
|
215
86
|
);
|
|
87
|
+
expect(command).toContain("--notebook nb-one");
|
|
88
|
+
expect(command).not.toContain("stable-one");
|
|
89
|
+
expect(command).not.toContain("--with-token");
|
|
90
|
+
});
|
|
91
|
+
it("uses the CLI without injected configuration", () => {
|
|
92
|
+
vi.stubEnv("DEV", true);
|
|
93
|
+
try {
|
|
94
|
+
const command = getTerminalCommand(
|
|
95
|
+
"claude",
|
|
96
|
+
CONNECTION_WITHOUT_TARGET,
|
|
97
|
+
false,
|
|
98
|
+
);
|
|
99
|
+
expect(command).toContain("uv run marimo pair prompt");
|
|
100
|
+
expect(command).not.toMatch(
|
|
101
|
+
/--claude|MARIMO_PAIR_NEXT|--file|--session|--notebook/,
|
|
102
|
+
);
|
|
103
|
+
} finally {
|
|
104
|
+
vi.unstubAllEnvs();
|
|
105
|
+
}
|
|
216
106
|
});
|
|
217
107
|
});
|
|
218
108
|
|
|
219
109
|
describe("getRawPrompt", () => {
|
|
220
|
-
it("
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
command: "custom marimo",
|
|
231
|
-
templates: {
|
|
232
|
-
...PAIR_PREVIEW.templates,
|
|
233
|
-
prompt: "{command}\n{url}\n{session}{authentication}",
|
|
234
|
-
},
|
|
110
|
+
it("does not interpret placeholders in inserted values", () => {
|
|
111
|
+
const prompt = getRawPrompt(
|
|
112
|
+
{ url: "http://host/{session}", notebook: "{command}/it's" },
|
|
113
|
+
"tok'en {session} $&",
|
|
114
|
+
{
|
|
115
|
+
...PAIR_CONFIG,
|
|
116
|
+
command: "custom marimo",
|
|
117
|
+
templates: {
|
|
118
|
+
...PAIR_CONFIG.templates,
|
|
119
|
+
prompt: "{command}\n{url}\n{notebook}{authentication}",
|
|
235
120
|
},
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
expect(prompt).toBe(
|
|
124
|
+
"custom marimo\nhttp://host/{session}\nNotebook: {command}/it's\n" +
|
|
125
|
+
`\n\nFor authenticated notebook discovery, set \`export MARIMO_TOKEN='tok'"'"'en {session} $&'\` in the shell that runs marimo.`,
|
|
240
126
|
);
|
|
241
127
|
});
|
|
242
|
-
|
|
243
128
|
it.each([
|
|
244
|
-
[
|
|
129
|
+
[CONNECTION_WITHOUT_TARGET, ""],
|
|
130
|
+
[CONNECTION, "Notebook: nb-one\n"],
|
|
245
131
|
[
|
|
246
|
-
{ ...
|
|
247
|
-
"Session:
|
|
132
|
+
{ ...CONNECTION_WITHOUT_TARGET, session: "stable-one" },
|
|
133
|
+
"Session: stable-one\n",
|
|
248
134
|
],
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
expect(getRawPrompt(connection, null,
|
|
252
|
-
"Pair with me on this running marimo notebook.\n\n" +
|
|
253
|
-
`URL: http://localhost:8000\n${fields}\n` +
|
|
254
|
-
"Run `uvx marimo@latest pair --help` first.\n" +
|
|
255
|
-
"Use `uvx marimo@latest` for all marimo commands.\n\n" +
|
|
256
|
-
"Once connected, send a fun toast using `mo.status.toast(...)` (`import marimo as mo`).",
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it("renders the shared preview template with the session and no file", () => {
|
|
135
|
+
])("renders the shared CLI prompt for %j", (connection, target) => {
|
|
136
|
+
const expected = `Pair with me on this running marimo notebook.\n\nURL: http://localhost:8000\n${target}\nRun \`uvx marimo@latest pair --help\` first.\nUse \`uvx marimo@latest\` for all marimo commands.`;
|
|
137
|
+
expect(getRawPrompt(connection, null, PAIR_CONFIG)).toBe(expected);
|
|
261
138
|
expect(
|
|
262
|
-
getRawPrompt({ ...
|
|
263
|
-
).toBe(
|
|
264
|
-
"Pair with me on this running marimo notebook.\n\n" +
|
|
265
|
-
"URL: http://localhost:8000\n" +
|
|
266
|
-
"Session: s_ab12cd\n\n" +
|
|
267
|
-
"Run `uvx marimo@latest pair --help` first.\n" +
|
|
268
|
-
"Use `uvx marimo@latest` for all marimo commands.\n\n" +
|
|
269
|
-
"Once connected, send a fun toast using `mo.status.toast(...)` (`import marimo as mo`).",
|
|
270
|
-
);
|
|
139
|
+
getRawPrompt(connection, null, { ...PAIR_CONFIG, hostEnabled: false }),
|
|
140
|
+
).toBe(expected);
|
|
271
141
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("omits file targeting when the page URL has no file", () => {
|
|
284
|
-
const prompt = getRawPrompt(CONNECTION_WITHOUT_FILE, null);
|
|
285
|
-
expect(prompt).toContain("execute-code.sh --url http://localhost:8000");
|
|
286
|
-
expect(prompt).not.toContain("--file");
|
|
287
|
-
expect(prompt).not.toContain("--session");
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("omits the token hint when there is no token", () => {
|
|
291
|
-
const prompt = getRawPrompt(CONNECTION, null);
|
|
292
|
-
expect(prompt).not.toContain("--token");
|
|
293
|
-
expect(prompt).not.toContain("auth token");
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it("includes a file-scoped token hint when a token is present", () => {
|
|
297
|
-
const prompt = getRawPrompt(CONNECTION, "secret-token");
|
|
298
|
-
expect(prompt).toContain(
|
|
299
|
-
"execute-code.sh --url http://localhost:8000 --file notebooks/example.py --token secret-token",
|
|
300
|
-
);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("shell-escapes a token containing a single quote", () => {
|
|
304
|
-
const prompt = getRawPrompt(CONNECTION, "tok'en");
|
|
305
|
-
expect(prompt).toContain(`--token 'tok'"'"'en'`);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it("matches the CLI prompt shape", () => {
|
|
309
|
-
const prompt = getRawPrompt(CONNECTION, null);
|
|
310
|
-
expect(prompt).toMatchInlineSnapshot(`
|
|
311
|
-
"Use the /marimo-pair skill to pair-program on a running marimo notebook.
|
|
312
|
-
|
|
313
|
-
Connect to the notebook at: http://localhost:8000 (file notebooks/example.py)
|
|
314
|
-
|
|
315
|
-
Use \`execute-code.sh --url http://localhost:8000 --file notebooks/example.py\` from the marimo-pair skill to execute code in the notebook.
|
|
316
|
-
|
|
317
|
-
Once you are connected, send a fun toast (mo.status.toast(...)) to the user inside marimo letting them know you're ready to pair."
|
|
318
|
-
`);
|
|
142
|
+
it("matches the shared prompt without injected templates", () => {
|
|
143
|
+
vi.stubEnv("DEV", false);
|
|
144
|
+
try {
|
|
145
|
+
expect(getRawPrompt(CONNECTION, "secret-token")).toBe(
|
|
146
|
+
getRawPrompt(CONNECTION, "secret-token", PAIR_CONFIG),
|
|
147
|
+
);
|
|
148
|
+
} finally {
|
|
149
|
+
vi.unstubAllEnvs();
|
|
150
|
+
}
|
|
319
151
|
});
|
|
320
152
|
});
|
|
321
153
|
|
|
@@ -324,12 +156,8 @@ describe("maskToken", () => {
|
|
|
324
156
|
expect(maskToken("ab")).toBe("****");
|
|
325
157
|
expect(maskToken("abcd")).toBe("****");
|
|
326
158
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
it("caps the number of mask characters at eight", () => {
|
|
333
|
-
expect(maskToken("0123456789abcdef")).toBe("********cdef");
|
|
334
|
-
});
|
|
159
|
+
it("reveals only the last four characters", () =>
|
|
160
|
+
expect(maskToken("abcdefgh")).toBe("****efgh"));
|
|
161
|
+
it("caps mask characters at eight", () =>
|
|
162
|
+
expect(maskToken("0123456789abcdef")).toBe("********cdef"));
|
|
335
163
|
});
|