@marimo-team/islands 0.24.3-dev50 → 0.24.3-dev52
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-BW0xr_AR.js → ErrorBoundary-Bfp74qVG.js} +1 -1
- package/dist/{code-visibility-BdKqYmPi.js → code-visibility-qtEE1_Zi.js} +1 -1
- package/dist/{common-D9PY0k7A.js → common-BmP252R5.js} +1 -1
- package/dist/{data-grid-overlay-editor-BZiw7m3d.js → data-grid-overlay-editor-DtaTjDfe.js} +1 -1
- package/dist/{feedback-button-BAHeJT2Q.js → feedback-button-D_YQdd5e.js} +2 -2
- package/dist/{glide-data-editor-Bp9hhneH.js → glide-data-editor-CV4eSJ7N.js} +3 -3
- package/dist/{globals-BUgvsAck.js → globals-DdrpcXQY.js} +1 -1
- package/dist/main.js +6 -6
- package/dist/{number-overlay-editor-C743iqsh.js → number-overlay-editor-DXWRr4gK.js} +1 -1
- package/dist/{reveal-component-CgMzL2w5.js → reveal-component-DLxDt6wQ.js} +2 -2
- package/package.json +1 -1
- package/src/__tests__/fixtures/pair-preview.ts +19 -0
- package/src/__tests__/mount.test.ts +18 -0
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +115 -0
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +218 -0
- package/src/components/editor/actions/pair-with-agent-commands.ts +46 -9
- package/src/components/editor/actions/pair-with-agent-modal.tsx +49 -23
- package/src/core/config/pair.ts +19 -0
- package/src/mount.tsx +3 -0
|
@@ -135,7 +135,7 @@ function F(e) {
|
|
|
135
135
|
}
|
|
136
136
|
//#endregion
|
|
137
137
|
//#region src/components/editor/boundary/ErrorBoundary.tsx
|
|
138
|
-
var I = (0, T.lazy)(() => import("./feedback-button-
|
|
138
|
+
var I = (0, T.lazy)(() => import("./feedback-button-D_YQdd5e.js").then((e) => ({ default: e.FeedbackModal }))), L = (e) => {
|
|
139
139
|
let t = (0, w.c)(2), n;
|
|
140
140
|
return t[0] === e.children ? n = t[1] : (n = /* @__PURE__ */ (0, j.jsx)(O, {
|
|
141
141
|
FallbackComponent: R,
|
|
@@ -18,7 +18,7 @@ function x() {
|
|
|
18
18
|
return e.warn("Failed to get version from mount config"), null;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
var S = x() || "0.24.3-
|
|
21
|
+
var S = x() || "0.24.3-dev52", C = u(S), w = u(!0);
|
|
22
22
|
u(null);
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region src/core/wasm/state.ts
|
|
@@ -12,7 +12,7 @@ import { t as dt } from "./check-B7cIK6gb.js";
|
|
|
12
12
|
import { A as ft, C as pt, D as mt, M as ht, O as gt, S as _t, _ as vt, b as yt, c as bt, d as xt, g as St, h as Ct, i as wt, m as Tt, n as Et, p as Dt, s as Ot, t as kt, u as At, v as jt, y as Mt } from "./select-C6plhmZr.js";
|
|
13
13
|
import { $ as Nt, $t as Pt, A as Ft, At as It, B as Lt, Bt as Rt, Ct as zt, D as Bt, Dt as Vt, E as Ht, Et as Ut, F as Wt, Ft as Gt, G as Kt, Gt as qt, H as Jt, Ht as Yt, I as Xt, It as Zt, Jt as Qt, K as $t, Kt as en, L as tn, Lt as nn, M as rn, Mt as an, N as on, Nt as sn, O as cn, Ot as ln, P as un, Pt as dn, Q as fn, Qt as pn, R as mn, Rt as hn, S as gn, St as _n, T as vn, Tt as yn, U as bn, Ut as xn, V as Sn, Vt as Cn, W as wn, Wt as Tn, X as En, Xt as Dn, Y as On, Yt as kn, Z as An, Zt as jn, _ as Mn, _t as Nn, a as Pn, an as Fn, b as In, bt as Ln, cn as Rn, ct as zn, d as Bn, dn as Vn, dt as Hn, en as Un, et as Wn, f as Gn, fn as Kn, ft as qn, gt as Jn, h as Yn, ht as Xn, i as Zn, it as Qn, j as $n, jt as er, k as tr, kt as nr, l as rr, ln as ir, lt as ar, mt as or, n as sr, nn as cr, nt as lr, o as ur, on as dr, ot as fr, pn as pr, pt as mr, qt as hr, rn as gr, rt as _r, s as vr, sn as yr, st as br, t as xr, tn as Sr, tt as Cr, u as wr, un as Tr, ut as Er, vt as Dr, w as Or, wt as kr, x as Ar, xt as jr, y as Mr, yt as Nr, z as Pr, zt as Fr } from "./dropdown-menu-CZgjYSAh.js";
|
|
14
14
|
import { _ as Ir, a as Lr, c as Rr, d as zr, i as Br, n as Vr, o as Hr, p as Ur, r as Wr, s as Gr, t as Kr } from "./toDate-WXg1yLcI.js";
|
|
15
|
-
import { n as qr, t as Jr } from "./ErrorBoundary-
|
|
15
|
+
import { n as qr, t as Jr } from "./ErrorBoundary-Bfp74qVG.js";
|
|
16
16
|
import { t as Yr } from "./clsx-CoeGtWvn.js";
|
|
17
17
|
import { t as Xr } from "./react-dom-BUgJ-ab_.js";
|
|
18
18
|
import { t as Zr } from "./jsx-runtime-DgDPJV9S.js";
|
|
@@ -2,7 +2,7 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
|
2
2
|
import { t } from "./react-ofJbxM4x.js";
|
|
3
3
|
import { t as n } from "./react-dom-BUgJ-ab_.js";
|
|
4
4
|
import { c as r, l as i, u as a } from "./data-grid-types-BucVmDDp.js";
|
|
5
|
-
import { a as o, i as s, n as c, r as l } from "./glide-data-editor-
|
|
5
|
+
import { a as o, i as s, n as c, r as l } from "./glide-data-editor-CV4eSJ7N.js";
|
|
6
6
|
//#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/data-grid-overlay-editor-style.js
|
|
7
7
|
var u = /* @__PURE__ */ e(t(), 1), d = /* @__PURE__ */ e(n(), 1), f = /*#__PURE__*/ o("div")({
|
|
8
8
|
name: "DataGridOverlayEditorStyle",
|
|
@@ -5,12 +5,12 @@ import { t as i } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
|
5
5
|
import { t as a } from "./copy-icon-CLdENY__.js";
|
|
6
6
|
import { Bt as o, H as s, Ht as c, L as l, Ot as u, V as d, d as f, hn as p } from "./cells-BSgNCBZ8.js";
|
|
7
7
|
import { b as m } from "./select-C6plhmZr.js";
|
|
8
|
-
import { n as h, t as ee } from "./globals-
|
|
8
|
+
import { n as h, t as ee } from "./globals-DdrpcXQY.js";
|
|
9
9
|
import { t as g } from "./jsx-runtime-DgDPJV9S.js";
|
|
10
10
|
import { C as _, _ as te, i as v } from "./useTheme-DzXmYZ5V.js";
|
|
11
11
|
import { a as ne, i as re, n as ie, r as ae } from "./dialog-CgNq6y9-.js";
|
|
12
12
|
import { t as oe } from "./useAsyncData-BD6AStMt.js";
|
|
13
|
-
import { t as se } from "./code-visibility-
|
|
13
|
+
import { t as se } from "./code-visibility-qtEE1_Zi.js";
|
|
14
14
|
import { i as ce, n as le } from "./error-entries-iJn-Nryx.js";
|
|
15
15
|
//#region src/components/ui/skeleton.tsx
|
|
16
16
|
var y = /* @__PURE__ */ e(r(), 1), b = i(), x = g(), ue = (e) => {
|
|
@@ -6,7 +6,7 @@ import { i as s, n as c, r as l } from "./copy-icon-CLdENY__.js";
|
|
|
6
6
|
import { C as u, c as d, i as f, n as p, s as m, t as h, x as g } from "./select-C6plhmZr.js";
|
|
7
7
|
import { d as _, h as v, i as y, l as b, n as x, o as S, s as C, t as w, u as T } from "./dropdown-menu-CZgjYSAh.js";
|
|
8
8
|
import { c as E, d as D, l as O, n as k, o as A, s as ee, t as j, u as te } from "./glide-utils-B3rUT5PI.js";
|
|
9
|
-
import { n as ne, t as re } from "./ErrorBoundary-
|
|
9
|
+
import { n as ne, t as re } from "./ErrorBoundary-Bfp74qVG.js";
|
|
10
10
|
import { t as M } from "./react-dom-BUgJ-ab_.js";
|
|
11
11
|
import { t as N } from "./jsx-runtime-DgDPJV9S.js";
|
|
12
12
|
import { t as ie } from "./label-BJ49YTvk.js";
|
|
@@ -7062,7 +7062,7 @@ function rs(e, t, n, r, i) {
|
|
|
7062
7062
|
}
|
|
7063
7063
|
//#endregion
|
|
7064
7064
|
//#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/data-editor/data-editor.js
|
|
7065
|
-
var is = /* @__PURE__ */ e(_r(), 1), as = /* @__PURE__ */ e(xr(), 1), os = K.lazy(async () => await import("./data-grid-overlay-editor-
|
|
7065
|
+
var is = /* @__PURE__ */ e(_r(), 1), as = /* @__PURE__ */ e(xr(), 1), os = K.lazy(async () => await import("./data-grid-overlay-editor-DtaTjDfe.js")), ss = 0;
|
|
7066
7066
|
function cs(e) {
|
|
7067
7067
|
return (0, is.default)((0, as.default)((0, as.default)(e).filter((e) => e.span !== void 0).map((e) => (0, Za.default)((e.span?.[0] ?? 0) + 1, (e.span?.[1] ?? 0) + 1))));
|
|
7068
7068
|
}
|
|
@@ -9293,7 +9293,7 @@ function Vs(e, t, n, r, i) {
|
|
|
9293
9293
|
}
|
|
9294
9294
|
//#endregion
|
|
9295
9295
|
//#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/cells/number-cell.js
|
|
9296
|
-
var Hs = K.lazy(async () => await import("./number-overlay-editor-
|
|
9296
|
+
var Hs = K.lazy(async () => await import("./number-overlay-editor-DXWRr4gK.js")), Us = {
|
|
9297
9297
|
getAccessibilityString: (e) => e.data?.toString() ?? "",
|
|
9298
9298
|
kind: W.Number,
|
|
9299
9299
|
needsHover: (e) => e.hoverEffect === !0,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { g as e } from "./tooltip-qB8NlSqV.js";
|
|
2
2
|
import { _ as t } from "./useTheme-DzXmYZ5V.js";
|
|
3
|
-
import { r as n } from "./code-visibility-
|
|
3
|
+
import { r as n } from "./code-visibility-qtEE1_Zi.js";
|
|
4
4
|
var r = e("triangle-alert", [
|
|
5
5
|
["path", {
|
|
6
6
|
d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",
|
package/dist/main.js
CHANGED
|
@@ -7,7 +7,7 @@ import { C as T, F as E, G as D, I as O, J as k, K as A, L as j, P as M, R as N,
|
|
|
7
7
|
import { a as se, f as ce, g as V, i as le, n as ue, o as de, p as fe, r as pe, t as me } from "./tooltip-qB8NlSqV.js";
|
|
8
8
|
import { $ as he, $t as ge, At as _e, B as ve, C as ye, D as be, Dt as xe, F as Se, G as Ce, Gt as we, I as Te, J as Ee, L as De, Mt as Oe, Nt as ke, O as Ae, Ot as je, P as Me, Q as Ne, Qt as Pe, S as Fe, T as Ie, Tt as Le, U as Re, Ut as ze, W as Be, Wt as Ve, X as He, Xt as Ue, Y as We, Yt as Ge, Zt as Ke, _n as qe, _t as Je, an as Ye, bn as Xe, c as Ze, cn as Qe, ct as $e, d as et, dn as tt, dt as nt, f as rt, fn as it, ft as at, g as ot, gn as st, gt as ct, h as lt, in as ut, it as dt, jt as ft, k as pt, kt as mt, l as ht, m as gt, mn as _t, n as vt, o as yt, on as bt, p as xt, pn as St, pt as Ct, q as wt, qt as Tt, r as Et, rn as Dt, rt as Ot, u as kt, v as At, w as jt, xt as Mt, yn as Nt, zt as Pt } from "./cells-BSgNCBZ8.js";
|
|
9
9
|
import { D as Ft, E as It, O as Lt, S as Rt, T as zt, _ as Bt, a as Vt, c as Ht, d as Ut, f as Wt, g as Gt, h as Kt, i as qt, l as Jt, m as Yt, n as Xt, o as Zt, p as Qt, r as $t, s as en, t as tn, u as nn, v as rn, w as an, x as on, y as sn } from "./spec-CAYwFeV6.js";
|
|
10
|
-
import { $t as cn, A as ln, At as un, B as dn, Bt as fn, C as pn, Ct as mn, D as hn, Dt as gn, E as _n, Et as vn, F as yn, Ft as bn, G as xn, Gt as Sn, H as Cn, Ht as wn, I as Tn, It as En, J as Dn, Jt as On, K as kn, Kt as An, L as jn, Lt as Mn, M as Nn, Mt as Pn, N as Fn, Nt as In, O as Ln, Ot as Rn, P as zn, Pt as Bn, Qt as Vn, R as Hn, Rt as Un, S as Wn, St as Gn, T as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Zt as ir, _ as ar, _t as or, an as sr, at as cr, b as lr, c as ur, cn as dr, ct as fr, d as pr, dn as mr, dt as hr, en as gr, et as _r, f as vr, fn as yr, ft as br, g as xr, gt as Sr, h as Cr, hn as wr, ht as Tr, in as Er, it as Dr, j as Or, jt as kr, k as Ar, kt as jr, ln as Mr, lt as Nr, m as Pr, mn as Fr, mt as Ir, nn as Lr, nt as Rr, ot as zr, p as Br, pn as Vr, pt as Hr, q as Ur, qt as Wr, r as Gr, rn as Kr, rt as qr, sn as Jr, st as Yr, t as Xr, tn as Zr, tt as Qr, u as $r, un as ei, ut as ti, v as ni, vt as ri, w as ii, wt as ai, x as oi, y as si, yt as ci, z as li, zt as ui } from "./common-
|
|
10
|
+
import { $t as cn, A as ln, At as un, B as dn, Bt as fn, C as pn, Ct as mn, D as hn, Dt as gn, E as _n, Et as vn, F as yn, Ft as bn, G as xn, Gt as Sn, H as Cn, Ht as wn, I as Tn, It as En, J as Dn, Jt as On, K as kn, Kt as An, L as jn, Lt as Mn, M as Nn, Mt as Pn, N as Fn, Nt as In, O as Ln, Ot as Rn, P as zn, Pt as Bn, Qt as Vn, R as Hn, Rt as Un, S as Wn, St as Gn, T as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Zt as ir, _ as ar, _t as or, an as sr, at as cr, b as lr, c as ur, cn as dr, ct as fr, d as pr, dn as mr, dt as hr, en as gr, et as _r, f as vr, fn as yr, ft as br, g as xr, gt as Sr, h as Cr, hn as wr, ht as Tr, in as Er, it as Dr, j as Or, jt as kr, k as Ar, kt as jr, ln as Mr, lt as Nr, m as Pr, mn as Fr, mt as Ir, nn as Lr, nt as Rr, ot as zr, p as Br, pn as Vr, pt as Hr, q as Ur, qt as Wr, r as Gr, rn as Kr, rt as qr, sn as Jr, st as Yr, t as Xr, tn as Zr, tt as Qr, u as $r, un as ei, ut as ti, v as ni, vt as ri, w as ii, wt as ai, x as oi, y as si, yt as ci, z as li, zt as ui } from "./common-BmP252R5.js";
|
|
11
11
|
import { c as di, i as fi, n as pi, o as mi, t as hi } from "./useLifecycle-DPgiiwwU.js";
|
|
12
12
|
import { t as gi } from "./check-B7cIK6gb.js";
|
|
13
13
|
import { A as _i, C as vi, D as yi, E as bi, M as xi, O as Si, S as Ci, T as wi, a as Ti, b as Ei, c as Di, i as Oi, k as ki, l as Ai, n as ji, o as Mi, r as Ni, s as Pi, t as Fi, w as Ii, x as Li } from "./select-C6plhmZr.js";
|
|
@@ -15,8 +15,8 @@ import { B as Ri, C as zi, J as Bi, Mt as Vi, Nt as Hi, S as Ui, Vt as Wi, W as
|
|
|
15
15
|
import { _ as ha, c as ga, d as _a, f as va, g as ya, h as ba, m as xa, u as Sa, v as Ca } from "./toDate-WXg1yLcI.js";
|
|
16
16
|
import { a as wa, c as Ta, d as Ea, f as Da, i as Oa, l as ka, n as Aa, o as ja, r as Ma, s as Na, t as Pa } from "./process-output-DFe2tVIY.js";
|
|
17
17
|
import { a as Fa, c as Ia, d as La, i as Ra, l as za, r as Ba, u as Va } from "./glide-utils-B3rUT5PI.js";
|
|
18
|
-
import { n as Ha, t as Ua } from "./ErrorBoundary-
|
|
19
|
-
import { n as Wa, t as Ga } from "./globals-
|
|
18
|
+
import { n as Ha, t as Ua } from "./ErrorBoundary-Bfp74qVG.js";
|
|
19
|
+
import { n as Wa, t as Ga } from "./globals-DdrpcXQY.js";
|
|
20
20
|
import { t as Ka } from "./react-dom-BUgJ-ab_.js";
|
|
21
21
|
import { t as qa } from "./jsx-runtime-DgDPJV9S.js";
|
|
22
22
|
import { B as Ja, C as H, Ct as Ya, D as Xa, E as Za, Et as Qa, F as U, G as $a, H as eo, I as to, L as no, M as W, N as ro, O as io, P as ao, Q as oo, R as so, S as G, St as co, Tt as lo, U as uo, W as fo, Y as po, Z as mo, _ as ho, a as go, b as _o, bt as vo, c as yo, d as bo, dt as xo, f as So, g as Co, gt as wo, h as To, i as Eo, j as K, k as Do, l as Oo, m as ko, n as Ao, o as jo, p as Mo, r as No, s as Po, u as Fo, v as Io, vt as Lo, w as Ro, wt as zo, x as Bo, xt as Vo, y as Ho, yt as Uo } from "./use-restore-focus-D5D9xEjZ.js";
|
|
@@ -40,7 +40,7 @@ import { t as Ns } from "./useAsyncData-BD6AStMt.js";
|
|
|
40
40
|
import { i as Ps, n as Fs, r as Is } from "./formats-BR5iGb4s.js";
|
|
41
41
|
import { M as Ls, N as Rs } from "./vega-loader.browser-D95WfUcj.js";
|
|
42
42
|
import { a as zs, n as Bs } from "./loader-D2W4oM-3.js";
|
|
43
|
-
import { c as Vs, d as Hs, i as Us, l as Ws, o as Gs, s as Ks, t as qs, u as Js } from "./code-visibility-
|
|
43
|
+
import { c as Vs, d as Hs, i as Us, l as Ws, o as Gs, s as Ks, t as qs, u as Js } from "./code-visibility-qtEE1_Zi.js";
|
|
44
44
|
import { t as Ys } from "./useDeepCompareMemoize-D_HNdyvI.js";
|
|
45
45
|
import { t as Xs } from "./esm-BElI3qKH.js";
|
|
46
46
|
//#region ../node_modules/.pnpm/iconify-icon@2.3.0/node_modules/iconify-icon/dist/iconify-icon.mjs
|
|
@@ -10458,7 +10458,7 @@ function RC(e, t) {
|
|
|
10458
10458
|
}
|
|
10459
10459
|
//#endregion
|
|
10460
10460
|
//#region src/plugins/impl/DataEditorPlugin.tsx
|
|
10461
|
-
var zC = J.lazy(() => import("./glide-data-editor-
|
|
10461
|
+
var zC = J.lazy(() => import("./glide-data-editor-CV4eSJ7N.js").then((e) => e.t)), BC = xS("marimo-data-editor", { cssStyles: [WS] }).withData(W({
|
|
10462
10462
|
initialValue: W({ edits: G(W({
|
|
10463
10463
|
rowIdx: K(),
|
|
10464
10464
|
columnId: U(),
|
|
@@ -29922,7 +29922,7 @@ function bR(e) {
|
|
|
29922
29922
|
}
|
|
29923
29923
|
//#endregion
|
|
29924
29924
|
//#region src/components/editor/renderers/slides-layout/slides-layout.tsx
|
|
29925
|
-
var xR = J.lazy(() => import("./reveal-component-
|
|
29925
|
+
var xR = J.lazy(() => import("./reveal-component-DLxDt6wQ.js")), SR = (e) => {
|
|
29926
29926
|
let t = (0, X.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = rs(Ce), s = a === "read" || o, c = rs(xt) > 1, [l, u] = (0, J.useState)(null), d;
|
|
29927
29927
|
t[0] !== i || t[1] !== n ? (d = vP(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
|
|
29928
29928
|
let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
2
2
|
import { t } from "./react-ofJbxM4x.js";
|
|
3
|
-
import { a as n } from "./glide-data-editor-
|
|
3
|
+
import { a as n } from "./glide-data-editor-CV4eSJ7N.js";
|
|
4
4
|
//#region ../node_modules/.pnpm/@glideapps+glide-data-grid@6.0.4-alpha24_lodash@4.17.21_marked@15.0.12_react-dom@19.2.8_b9e6aec46d952f631ce71b0585d2a8e5/node_modules/@glideapps/glide-data-grid/dist/esm/internal/data-grid-overlay-editor/private/number-overlay-editor-style.js
|
|
5
5
|
var r = /* @__PURE__ */ e(t()), i = /*#__PURE__*/ n("div")({
|
|
6
6
|
name: "NumberOverlayEditorStyle",
|
|
@@ -4,13 +4,13 @@ import { t as o } from "./react-ofJbxM4x.js";
|
|
|
4
4
|
import { t as s } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
5
|
import { g as c, t as l } from "./tooltip-qB8NlSqV.js";
|
|
6
6
|
import { G as u, w as d } from "./cells-BSgNCBZ8.js";
|
|
7
|
-
import { $ as f, Q as p, Z as m, an as h, i as g, l as _, ln as v, n as y, on as b, r as x, t as S, u as C } from "./common-
|
|
7
|
+
import { $ as f, Q as p, Z as m, an as h, i as g, l as _, ln as v, n as y, on as b, r as x, t as S, u as C } from "./common-BmP252R5.js";
|
|
8
8
|
import { M as w } from "./select-C6plhmZr.js";
|
|
9
9
|
import { J as T } from "./dropdown-menu-CZgjYSAh.js";
|
|
10
10
|
import { t as E } from "./jsx-runtime-DgDPJV9S.js";
|
|
11
11
|
import { q as ee } from "./use-restore-focus-D5D9xEjZ.js";
|
|
12
12
|
import { A as D, C as O, b as k } from "./useTheme-DzXmYZ5V.js";
|
|
13
|
-
import { a as A, n as te, t as ne } from "./code-visibility-
|
|
13
|
+
import { a as A, n as te, t as ne } from "./code-visibility-qtEE1_Zi.js";
|
|
14
14
|
/* empty css */
|
|
15
15
|
var j = c("sticky-note", [["path", {
|
|
16
16
|
d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pairPreviewSchema } from "@/core/config/pair";
|
|
6
|
+
|
|
7
|
+
// Read the Python-owned resource only in tests to check the shared contract.
|
|
8
|
+
export const PAIR_PREVIEW = pairPreviewSchema.parse({
|
|
9
|
+
command: "uv run marimo",
|
|
10
|
+
templates: JSON.parse(
|
|
11
|
+
readFileSync(
|
|
12
|
+
path.resolve(
|
|
13
|
+
import.meta.dirname,
|
|
14
|
+
"../../../../marimo/_cli/pair/prompt.json",
|
|
15
|
+
),
|
|
16
|
+
"utf8",
|
|
17
|
+
),
|
|
18
|
+
),
|
|
19
|
+
});
|
|
@@ -8,6 +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 { WebSocketState } from "@/core/websocket/types";
|
|
11
|
+
import { pairPreviewAtom } from "@/core/config/pair";
|
|
12
|
+
import { PAIR_PREVIEW } from "./fixtures/pair-preview";
|
|
11
13
|
import { mount, visibleForTesting } from "../mount";
|
|
12
14
|
|
|
13
15
|
// Mock React DOM
|
|
@@ -82,6 +84,22 @@ describe("mount", () => {
|
|
|
82
84
|
mockElement,
|
|
83
85
|
);
|
|
84
86
|
|
|
87
|
+
it("hydrates the server's Pair preview configuration", () => {
|
|
88
|
+
const error = mountRead({ pairPreview: PAIR_PREVIEW });
|
|
89
|
+
|
|
90
|
+
expect(error).toBeUndefined();
|
|
91
|
+
expect(store.get(pairPreviewAtom)).toEqual(PAIR_PREVIEW);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("uses legacy Pair behavior when the host omits preview configuration", () => {
|
|
95
|
+
store.set(pairPreviewAtom, PAIR_PREVIEW);
|
|
96
|
+
|
|
97
|
+
const error = mountRead();
|
|
98
|
+
|
|
99
|
+
expect(error).toBeUndefined();
|
|
100
|
+
expect(store.get(pairPreviewAtom)).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
|
|
85
103
|
describe("connection state initialization", () => {
|
|
86
104
|
it("should set connection to CONNECTING when runtimeConfig has lazy=false", () => {
|
|
87
105
|
mount(
|
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
maskToken,
|
|
11
11
|
} from "../pair-with-agent-commands";
|
|
12
12
|
import { shellQuote } from "@/utils/shell";
|
|
13
|
+
import { PAIR_PREVIEW } from "@/__tests__/fixtures/pair-preview";
|
|
13
14
|
|
|
14
15
|
const CONNECTION: ConnectionInfo = {
|
|
15
16
|
url: "http://localhost:8000",
|
|
16
17
|
file: "notebooks/example.py",
|
|
18
|
+
session: "s_ab12cd",
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
const CONNECTION_WITHOUT_FILE: ConnectionInfo = {
|
|
@@ -104,6 +106,61 @@ describe("getMarimoCommand", () => {
|
|
|
104
106
|
});
|
|
105
107
|
|
|
106
108
|
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 uv run marimo pair prompt \
|
|
133
|
+
--url http://localhost:8000)"`,
|
|
134
|
+
);
|
|
135
|
+
} finally {
|
|
136
|
+
vi.unstubAllEnvs();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it.each([
|
|
141
|
+
["claude", "claude"],
|
|
142
|
+
["codex", "codex"],
|
|
143
|
+
["opencode", "opencode --prompt"],
|
|
144
|
+
] as const)(
|
|
145
|
+
"enables the preview inside the %s prompt command",
|
|
146
|
+
(agent, cli) => {
|
|
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 uv run marimo pair prompt \
|
|
156
|
+
--url http://localhost:8000 \
|
|
157
|
+
--file notebooks/example.py \
|
|
158
|
+
--session s_ab12cd \
|
|
159
|
+
--with-token)"`,
|
|
160
|
+
);
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
|
|
107
164
|
it("includes the url and file for each agent", () => {
|
|
108
165
|
expect(getTerminalCommand("claude", CONNECTION, false)).toBe(
|
|
109
166
|
`claude "$(uv run marimo pair prompt --url http://localhost:8000 --file notebooks/example.py --claude)"`,
|
|
@@ -169,6 +226,64 @@ describe("getTerminalCommand", () => {
|
|
|
169
226
|
});
|
|
170
227
|
|
|
171
228
|
describe("getRawPrompt", () => {
|
|
229
|
+
it("uses supplied templates without reinterpreting inserted values", () => {
|
|
230
|
+
expect(
|
|
231
|
+
getRawPrompt(
|
|
232
|
+
{
|
|
233
|
+
url: "http://host/{file}",
|
|
234
|
+
file: "{session}/it's.py",
|
|
235
|
+
session: "{command}",
|
|
236
|
+
},
|
|
237
|
+
"tok'en {file} $&",
|
|
238
|
+
{
|
|
239
|
+
...PAIR_PREVIEW,
|
|
240
|
+
command: "custom marimo",
|
|
241
|
+
templates: {
|
|
242
|
+
...PAIR_PREVIEW.templates,
|
|
243
|
+
prompt: "{command}\n{url}\n{file}{session}{authentication}",
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
),
|
|
247
|
+
).toBe(
|
|
248
|
+
"custom marimo\nhttp://host/{file}\nFile: {session}/it's.py\nSession: {command}\n" +
|
|
249
|
+
`\n\nFor authenticated Pair commands, set \`export MARIMO_TOKEN='tok'"'"'en {file} $&'\` in the shell that runs marimo.`,
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it.each([
|
|
254
|
+
[CONNECTION_WITHOUT_FILE, ""],
|
|
255
|
+
[
|
|
256
|
+
{ ...CONNECTION_WITHOUT_FILE, session: "s_ab12cd" },
|
|
257
|
+
"Session: s_ab12cd\n",
|
|
258
|
+
],
|
|
259
|
+
[
|
|
260
|
+
{ ...CONNECTION_WITHOUT_FILE, file: "notebook.py" },
|
|
261
|
+
"File: notebook.py\n",
|
|
262
|
+
],
|
|
263
|
+
])("omits absent preview fields for %j", (connection, fields) => {
|
|
264
|
+
expect(getRawPrompt(connection, null, PAIR_PREVIEW)).toBe(
|
|
265
|
+
"Pair with me on this running marimo notebook.\n\n" +
|
|
266
|
+
`URL: http://localhost:8000\n${fields}\n` +
|
|
267
|
+
"Run `uv run marimo pair --help` first.\n" +
|
|
268
|
+
"Use `uv run marimo` for all marimo commands.\n\n" +
|
|
269
|
+
"Once connected, send a fun toast using `mo.status.toast(...)` (`import marimo as mo`).",
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("renders the shared preview template with optional session context", () => {
|
|
274
|
+
expect(
|
|
275
|
+
getRawPrompt({ ...CONNECTION, session: "s_ab12cd" }, null, PAIR_PREVIEW),
|
|
276
|
+
).toBe(
|
|
277
|
+
"Pair with me on this running marimo notebook.\n\n" +
|
|
278
|
+
"URL: http://localhost:8000\n" +
|
|
279
|
+
"File: notebooks/example.py\n" +
|
|
280
|
+
"Session: s_ab12cd\n\n" +
|
|
281
|
+
"Run `uv run marimo pair --help` first.\n" +
|
|
282
|
+
"Use `uv run marimo` for all marimo commands.\n\n" +
|
|
283
|
+
"Once connected, send a fun toast using `mo.status.toast(...)` (`import marimo as mo`).",
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
172
287
|
it("references the file-scoped execute-code command", () => {
|
|
173
288
|
const prompt = getRawPrompt(CONNECTION, null);
|
|
174
289
|
expect(prompt).toContain(
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
fireEvent,
|
|
6
|
+
render,
|
|
7
|
+
screen,
|
|
8
|
+
waitFor,
|
|
9
|
+
within,
|
|
10
|
+
} from "@testing-library/react";
|
|
11
|
+
import { Provider } from "jotai";
|
|
12
|
+
import type React from "react";
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
|
+
import { PAIR_PREVIEW } from "@/__tests__/fixtures/pair-preview";
|
|
15
|
+
import { Dialog } from "@/components/ui/dialog";
|
|
16
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
17
|
+
import { pairPreviewAtom } from "@/core/config/pair";
|
|
18
|
+
import { getSessionId } from "@/core/kernel/session";
|
|
19
|
+
import { filenameAtom } from "@/core/saving/file-state";
|
|
20
|
+
import {
|
|
21
|
+
DEFAULT_RUNTIME_CONFIG,
|
|
22
|
+
runtimeConfigAtom,
|
|
23
|
+
} from "@/core/runtime/config";
|
|
24
|
+
import { store } from "@/core/state/jotai";
|
|
25
|
+
import { PairWithAgentModal } from "../pair-with-agent-modal";
|
|
26
|
+
import { copyToClipboard } from "@/utils/copy";
|
|
27
|
+
|
|
28
|
+
vi.mock("@/utils/copy", () => ({
|
|
29
|
+
copyToClipboard: vi.fn().mockResolvedValue(undefined),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
function wrapper({ children }: { children: React.ReactNode }) {
|
|
33
|
+
return (
|
|
34
|
+
<Provider store={store}>
|
|
35
|
+
<TooltipProvider>
|
|
36
|
+
<Dialog open={true}>{children}</Dialog>
|
|
37
|
+
</TooltipProvider>
|
|
38
|
+
</Provider>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("PairWithAgentModal", () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
vi.clearAllMocks();
|
|
45
|
+
store.set(pairPreviewAtom, PAIR_PREVIEW);
|
|
46
|
+
store.set(filenameAtom, "/project/my notebook.py");
|
|
47
|
+
store.set(runtimeConfigAtom, {
|
|
48
|
+
...DEFAULT_RUNTIME_CONFIG,
|
|
49
|
+
url: "http://localhost:8000",
|
|
50
|
+
});
|
|
51
|
+
window.history.replaceState({}, "", "/?file=notebook.py");
|
|
52
|
+
vi.stubGlobal(
|
|
53
|
+
"fetch",
|
|
54
|
+
vi.fn().mockResolvedValue(new Response(JSON.stringify({ token: null }))),
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
store.set(pairPreviewAtom, undefined);
|
|
60
|
+
store.set(filenameAtom, null);
|
|
61
|
+
vi.unstubAllGlobals();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
|
|
65
|
+
"omits skill installation and includes the current session in %s preview",
|
|
66
|
+
async (tab) => {
|
|
67
|
+
await act(async () => {
|
|
68
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
69
|
+
});
|
|
70
|
+
fireEvent.mouseDown(screen.getByRole("tab", { name: tab }), {
|
|
71
|
+
button: 0,
|
|
72
|
+
ctrlKey: false,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const panel = screen.getByRole("tabpanel");
|
|
76
|
+
expect(within(panel).queryAllByText(/skill/i)).toEqual([]);
|
|
77
|
+
expect(panel).toHaveTextContent(getSessionId());
|
|
78
|
+
expect(panel).toHaveTextContent(
|
|
79
|
+
tab === "Prompt" ? "File: notebook.py" : "--file notebook.py",
|
|
80
|
+
);
|
|
81
|
+
expect(panel).not.toHaveTextContent("/project/my notebook.py");
|
|
82
|
+
expect(panel).toHaveTextContent(
|
|
83
|
+
tab === "Prompt" ? "1. Copy this prompt" : "1. Run in your terminal",
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
|
|
89
|
+
"includes the known notebook file in %s preview when the URL has no file",
|
|
90
|
+
async (tab) => {
|
|
91
|
+
window.history.replaceState({}, "", "/");
|
|
92
|
+
await act(async () => {
|
|
93
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
94
|
+
});
|
|
95
|
+
fireEvent.mouseDown(screen.getByRole("tab", { name: tab }), {
|
|
96
|
+
button: 0,
|
|
97
|
+
ctrlKey: false,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const panel = screen.getByRole("tabpanel");
|
|
101
|
+
const fileHint =
|
|
102
|
+
tab === "Prompt"
|
|
103
|
+
? "File: /project/my notebook.py"
|
|
104
|
+
: "--file '/project/my notebook.py'";
|
|
105
|
+
expect(panel).toHaveTextContent(fileHint);
|
|
106
|
+
fireEvent.click(
|
|
107
|
+
within(panel).getByRole("button", {
|
|
108
|
+
name: tab === "Prompt" ? "Copy prompt" : "Copy command",
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
|
|
112
|
+
expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(fileHint);
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
it.each([null, ""])(
|
|
117
|
+
"omits the file when the URL and notebook filename are absent (%s)",
|
|
118
|
+
async (filename) => {
|
|
119
|
+
window.history.replaceState({}, "", "/");
|
|
120
|
+
store.set(filenameAtom, filename);
|
|
121
|
+
await act(async () => {
|
|
122
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
123
|
+
});
|
|
124
|
+
expect(screen.getByRole("tabpanel")).not.toHaveTextContent("--file");
|
|
125
|
+
fireEvent.mouseDown(screen.getByRole("tab", { name: "Prompt" }), {
|
|
126
|
+
button: 0,
|
|
127
|
+
ctrlKey: false,
|
|
128
|
+
});
|
|
129
|
+
expect(screen.getByRole("tabpanel")).not.toHaveTextContent("File:");
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
|
|
134
|
+
"preserves the %s skill flow without preview configuration",
|
|
135
|
+
async (tab) => {
|
|
136
|
+
store.set(pairPreviewAtom, undefined);
|
|
137
|
+
window.history.replaceState({}, "", "/");
|
|
138
|
+
await act(async () => {
|
|
139
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
140
|
+
});
|
|
141
|
+
fireEvent.mouseDown(screen.getByRole("tab", { name: tab }), {
|
|
142
|
+
button: 0,
|
|
143
|
+
ctrlKey: false,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const panel = screen.getByRole("tabpanel");
|
|
147
|
+
expect(
|
|
148
|
+
within(panel).getByText("npx skills add marimo-team/marimo-pair"),
|
|
149
|
+
).toBeVisible();
|
|
150
|
+
expect(panel).not.toHaveTextContent(getSessionId());
|
|
151
|
+
expect(panel).not.toHaveTextContent("MARIMO_PAIR_NEXT");
|
|
152
|
+
expect(panel).not.toHaveTextContent("/project/my notebook.py");
|
|
153
|
+
expect(panel).toHaveTextContent(
|
|
154
|
+
tab === "Prompt" ? "2. Copy this prompt" : "2. Run in your terminal",
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
it.each([true, false])(
|
|
160
|
+
"masks the raw token but copies it intact (preview=%s)",
|
|
161
|
+
async (preview) => {
|
|
162
|
+
store.set(pairPreviewAtom, preview ? PAIR_PREVIEW : undefined);
|
|
163
|
+
vi.mocked(fetch).mockResolvedValue(
|
|
164
|
+
new Response(JSON.stringify({ token: "secret-token" })),
|
|
165
|
+
);
|
|
166
|
+
await act(async () => {
|
|
167
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
168
|
+
});
|
|
169
|
+
fireEvent.mouseDown(screen.getByRole("tab", { name: "Prompt" }), {
|
|
170
|
+
button: 0,
|
|
171
|
+
ctrlKey: false,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const panel = screen.getByRole("tabpanel");
|
|
175
|
+
expect(panel).toHaveTextContent("********oken");
|
|
176
|
+
expect(panel).not.toHaveTextContent("secret-token");
|
|
177
|
+
fireEvent.click(
|
|
178
|
+
within(panel).getByRole("button", { name: "Copy prompt" }),
|
|
179
|
+
);
|
|
180
|
+
await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
|
|
181
|
+
expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(
|
|
182
|
+
preview ? "export MARIMO_TOKEN=secret-token" : "--token secret-token",
|
|
183
|
+
);
|
|
184
|
+
expect(vi.mocked(copyToClipboard).mock.calls[0][0]).not.toContain(
|
|
185
|
+
"********oken",
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
it("copies a preview terminal command without the token and lets the user copy the token separately", async () => {
|
|
191
|
+
vi.mocked(fetch).mockResolvedValue(
|
|
192
|
+
new Response(JSON.stringify({ token: "secret-token" })),
|
|
193
|
+
);
|
|
194
|
+
await act(async () => {
|
|
195
|
+
render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const panel = screen.getByRole("tabpanel");
|
|
199
|
+
expect(panel).toHaveTextContent("2. Paste when prompted for a token");
|
|
200
|
+
expect(panel).not.toHaveTextContent("secret-token");
|
|
201
|
+
fireEvent.click(
|
|
202
|
+
within(panel).getByRole("button", { name: "Copy command" }),
|
|
203
|
+
);
|
|
204
|
+
await waitFor(() =>
|
|
205
|
+
expect(copyToClipboard).toHaveBeenCalledWith(
|
|
206
|
+
String.raw`claude "$(MARIMO_PAIR_NEXT=1 uv run marimo pair prompt \
|
|
207
|
+
--url http://localhost:8000/ \
|
|
208
|
+
--file notebook.py \
|
|
209
|
+
--session ${getSessionId()} \
|
|
210
|
+
--with-token)"`,
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
fireEvent.click(within(panel).getByRole("button", { name: "Copy token" }));
|
|
214
|
+
await waitFor(() =>
|
|
215
|
+
expect(copyToClipboard).toHaveBeenLastCalledWith("secret-token"),
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { assertNever } from "@/utils/assertNever";
|
|
4
4
|
import { KnownQueryParams } from "@/core/constants";
|
|
5
5
|
import { shellQuote } from "@/utils/shell";
|
|
6
|
+
import type { PairPreviewConfig } from "@/core/config/pair";
|
|
6
7
|
|
|
7
8
|
export type AgentTab = "claude" | "codex" | "opencode" | "prompt";
|
|
8
9
|
|
|
@@ -37,8 +38,9 @@ function getFileFlag(file: string | undefined): string {
|
|
|
37
38
|
/** Identifies the specific running notebook to pair on. */
|
|
38
39
|
export interface ConnectionInfo {
|
|
39
40
|
url: string;
|
|
40
|
-
/** The server's file key
|
|
41
|
+
/** The server's file key or notebook filename, when known. */
|
|
41
42
|
file?: string;
|
|
43
|
+
session?: string;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/**
|
|
@@ -47,19 +49,29 @@ export interface ConnectionInfo {
|
|
|
47
49
|
*/
|
|
48
50
|
export function getTerminalCommand(
|
|
49
51
|
agent: Exclude<AgentTab, "prompt">,
|
|
50
|
-
{ url, file }: ConnectionInfo,
|
|
52
|
+
{ url, file, session }: ConnectionInfo,
|
|
51
53
|
withToken: boolean,
|
|
54
|
+
preview?: PairPreviewConfig,
|
|
52
55
|
): string {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const base =
|
|
56
|
+
const command = preview?.command ?? getMarimoCommand();
|
|
57
|
+
const prefix = preview ? "MARIMO_PAIR_NEXT=1 " : "";
|
|
58
|
+
const base = [
|
|
59
|
+
`${prefix}${command} pair prompt`,
|
|
60
|
+
`--url ${shellQuote(url)}`,
|
|
61
|
+
file ? `--file ${shellQuote(file)}` : "",
|
|
62
|
+
preview && session ? `--session ${shellQuote(session)}` : "",
|
|
63
|
+
withToken ? "--with-token" : "",
|
|
64
|
+
preview ? "" : `--${agent}`,
|
|
65
|
+
]
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join(preview ? " \\\n " : " ");
|
|
56
68
|
switch (agent) {
|
|
57
69
|
case "claude":
|
|
58
|
-
return `claude "$(${base}
|
|
70
|
+
return `claude "$(${base})"`;
|
|
59
71
|
case "codex":
|
|
60
|
-
return `codex "$(${base}
|
|
72
|
+
return `codex "$(${base})"`;
|
|
61
73
|
case "opencode":
|
|
62
|
-
return `opencode --prompt "$(${base}
|
|
74
|
+
return `opencode --prompt "$(${base})"`;
|
|
63
75
|
default:
|
|
64
76
|
assertNever(agent);
|
|
65
77
|
}
|
|
@@ -71,9 +83,23 @@ export function getTerminalCommand(
|
|
|
71
83
|
* an agent behaves the same as the terminal commands.
|
|
72
84
|
*/
|
|
73
85
|
export function getRawPrompt(
|
|
74
|
-
{ url, file }: ConnectionInfo,
|
|
86
|
+
{ url, file, session }: ConnectionInfo,
|
|
75
87
|
token: string | null,
|
|
88
|
+
preview?: PairPreviewConfig,
|
|
76
89
|
): string {
|
|
90
|
+
if (preview) {
|
|
91
|
+
const { command, templates } = preview;
|
|
92
|
+
return formatPrompt(templates.prompt, {
|
|
93
|
+
command,
|
|
94
|
+
url,
|
|
95
|
+
file: file ? formatPrompt(templates.file, { file }) : "",
|
|
96
|
+
session: session ? formatPrompt(templates.session, { session }) : "",
|
|
97
|
+
authentication: token
|
|
98
|
+
? formatPrompt(templates.token, { token: shellQuote(token) })
|
|
99
|
+
: "",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
77
103
|
const fileFlag = getFileFlag(file);
|
|
78
104
|
const fileHint = file ? ` (file ${file})` : "";
|
|
79
105
|
const executeCmd = `execute-code.sh --url ${shellQuote(url)}${fileFlag}`;
|
|
@@ -91,6 +117,17 @@ export function getRawPrompt(
|
|
|
91
117
|
].join("\n");
|
|
92
118
|
}
|
|
93
119
|
|
|
120
|
+
function formatPrompt(
|
|
121
|
+
template: string,
|
|
122
|
+
values: Record<string, string>,
|
|
123
|
+
): string {
|
|
124
|
+
// Replace once so inserted file names or tokens cannot become placeholders.
|
|
125
|
+
return template.replaceAll(
|
|
126
|
+
/\{(\w+)\}/g,
|
|
127
|
+
(placeholder, key: string) => values[key] ?? placeholder,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
94
131
|
/** Mask all but the last 4 chars of a token for display. */
|
|
95
132
|
export function maskToken(token: string): string {
|
|
96
133
|
if (token.length <= 4) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
4
4
|
import React, { useEffect, useState } from "react";
|
|
5
|
+
import { useAtomValue } from "jotai";
|
|
5
6
|
import { Button } from "@/components/ui/button";
|
|
6
7
|
import {
|
|
7
8
|
DialogContent,
|
|
@@ -16,6 +17,9 @@ import { Events } from "@/utils/events";
|
|
|
16
17
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
17
18
|
import { asRemoteURL, useRuntimeManager } from "@/core/runtime/config";
|
|
18
19
|
import { API } from "@/core/network/api";
|
|
20
|
+
import { pairPreviewAtom } from "@/core/config/pair";
|
|
21
|
+
import { getSessionId } from "@/core/kernel/session";
|
|
22
|
+
import { useFilename } from "@/core/saving/filename";
|
|
19
23
|
import {
|
|
20
24
|
AGENT_LABELS,
|
|
21
25
|
AGENT_TABS,
|
|
@@ -49,11 +53,17 @@ export const PairWithAgentModal: React.FC<{
|
|
|
49
53
|
}> = ({ onClose }) => {
|
|
50
54
|
const [activeTab, setActiveTab] = useState<AgentTab>("claude");
|
|
51
55
|
const runtimeManager = useRuntimeManager();
|
|
56
|
+
const preview = useAtomValue(pairPreviewAtom);
|
|
57
|
+
const filename = useFilename();
|
|
58
|
+
const commandStep = preview ? 1 : 2;
|
|
52
59
|
const authToken = useAuthToken();
|
|
53
60
|
const hasToken = Boolean(authToken);
|
|
54
61
|
const connection: ConnectionInfo = {
|
|
55
62
|
url: runtimeManager.httpURL.toString(),
|
|
56
|
-
file:
|
|
63
|
+
file:
|
|
64
|
+
getFileFromURL(window.location.href) ??
|
|
65
|
+
(preview ? filename || undefined : undefined),
|
|
66
|
+
session: preview ? getSessionId() : undefined,
|
|
57
67
|
};
|
|
58
68
|
|
|
59
69
|
return (
|
|
@@ -94,23 +104,34 @@ export const PairWithAgentModal: React.FC<{
|
|
|
94
104
|
value={tab}
|
|
95
105
|
className="mt-4 flex flex-col gap-4"
|
|
96
106
|
>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
{!preview && (
|
|
108
|
+
<Step
|
|
109
|
+
index={1}
|
|
110
|
+
title="Install the skill"
|
|
111
|
+
hint="Run once per machine."
|
|
112
|
+
>
|
|
113
|
+
<CommandBlock command={SKILL_INSTALL} />
|
|
114
|
+
</Step>
|
|
115
|
+
)}
|
|
116
|
+
<Step index={commandStep} title="Run in your terminal">
|
|
105
117
|
<CommandBlock
|
|
106
|
-
command={getTerminalCommand(
|
|
118
|
+
command={getTerminalCommand(
|
|
119
|
+
tab,
|
|
120
|
+
connection,
|
|
121
|
+
hasToken,
|
|
122
|
+
preview,
|
|
123
|
+
)}
|
|
107
124
|
/>
|
|
108
125
|
</Step>
|
|
109
126
|
{hasToken && authToken && (
|
|
110
|
-
<Step
|
|
127
|
+
<Step
|
|
128
|
+
index={commandStep + 1}
|
|
129
|
+
title="Paste when prompted for a token"
|
|
130
|
+
>
|
|
111
131
|
<CommandBlock
|
|
112
132
|
command={authToken}
|
|
113
133
|
display={maskToken(authToken)}
|
|
134
|
+
copyLabel="Copy token"
|
|
114
135
|
/>
|
|
115
136
|
</Step>
|
|
116
137
|
)}
|
|
@@ -118,15 +139,17 @@ export const PairWithAgentModal: React.FC<{
|
|
|
118
139
|
))}
|
|
119
140
|
|
|
120
141
|
<TabsContent value="prompt" className="mt-4 flex flex-col gap-4">
|
|
142
|
+
{!preview && (
|
|
143
|
+
<Step
|
|
144
|
+
index={1}
|
|
145
|
+
title="Make sure the marimo-pair skill is available to your agent"
|
|
146
|
+
hint="Skip if your agent already has it."
|
|
147
|
+
>
|
|
148
|
+
<CommandBlock command={SKILL_INSTALL} />
|
|
149
|
+
</Step>
|
|
150
|
+
)}
|
|
121
151
|
<Step
|
|
122
|
-
index={
|
|
123
|
-
title="Make sure the marimo-pair skill is available to your agent"
|
|
124
|
-
hint="Skip if your agent already has it."
|
|
125
|
-
>
|
|
126
|
-
<CommandBlock command={SKILL_INSTALL} />
|
|
127
|
-
</Step>
|
|
128
|
-
<Step
|
|
129
|
-
index={2}
|
|
152
|
+
index={commandStep}
|
|
130
153
|
title="Copy this prompt into your agent"
|
|
131
154
|
hint={
|
|
132
155
|
hasToken
|
|
@@ -135,10 +158,11 @@ export const PairWithAgentModal: React.FC<{
|
|
|
135
158
|
}
|
|
136
159
|
>
|
|
137
160
|
<CommandBlock
|
|
138
|
-
command={getRawPrompt(connection, authToken)}
|
|
161
|
+
command={getRawPrompt(connection, authToken, preview)}
|
|
139
162
|
display={getRawPrompt(
|
|
140
163
|
connection,
|
|
141
164
|
authToken ? maskToken(authToken) : null,
|
|
165
|
+
preview,
|
|
142
166
|
)}
|
|
143
167
|
multiline={true}
|
|
144
168
|
/>
|
|
@@ -177,7 +201,8 @@ const CommandBlock: React.FC<{
|
|
|
177
201
|
command: string;
|
|
178
202
|
display?: string;
|
|
179
203
|
multiline?: boolean;
|
|
180
|
-
|
|
204
|
+
copyLabel?: string;
|
|
205
|
+
}> = ({ command, display, multiline = false, copyLabel = "Copy command" }) => {
|
|
181
206
|
const [copied, setCopied] = useState(false);
|
|
182
207
|
|
|
183
208
|
const copy = Events.stopPropagation(async (e) => {
|
|
@@ -195,6 +220,7 @@ const CommandBlock: React.FC<{
|
|
|
195
220
|
</pre>
|
|
196
221
|
<Tooltip content="Copied!" open={copied}>
|
|
197
222
|
<Button
|
|
223
|
+
aria-label="Copy prompt"
|
|
198
224
|
onClick={copy}
|
|
199
225
|
size="xs"
|
|
200
226
|
variant="ghost"
|
|
@@ -213,11 +239,11 @@ const CommandBlock: React.FC<{
|
|
|
213
239
|
|
|
214
240
|
return (
|
|
215
241
|
<div className="flex items-center gap-2 rounded-md bg-muted px-3 py-2 font-mono text-xs">
|
|
216
|
-
<code className="flex-1 select-all wrap-break-word">
|
|
242
|
+
<code className="flex-1 whitespace-pre-wrap select-all wrap-break-word">
|
|
217
243
|
{display ?? command}
|
|
218
244
|
</code>
|
|
219
245
|
<Tooltip content="Copied!" open={copied}>
|
|
220
|
-
<Button onClick={copy} size="xs" variant="ghost">
|
|
246
|
+
<Button aria-label={copyLabel} onClick={copy} size="xs" variant="ghost">
|
|
221
247
|
{copied ? (
|
|
222
248
|
<CheckIcon size={14} strokeWidth={1.5} />
|
|
223
249
|
) : (
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { atom } from "jotai";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
export const pairPreviewSchema = z.object({
|
|
7
|
+
command: z.string(),
|
|
8
|
+
templates: z.object({
|
|
9
|
+
prompt: z.string(),
|
|
10
|
+
file: z.string(),
|
|
11
|
+
session: z.string(),
|
|
12
|
+
token_file: z.string(),
|
|
13
|
+
token: z.string(),
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type PairPreviewConfig = z.infer<typeof pairPreviewSchema>;
|
|
18
|
+
|
|
19
|
+
export const pairPreviewAtom = atom<PairPreviewConfig | undefined>(undefined);
|
package/src/mount.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
userConfigAtom,
|
|
11
11
|
} from "@/core/config/config";
|
|
12
12
|
import { KnownQueryParams } from "@/core/constants";
|
|
13
|
+
import { pairPreviewAtom, pairPreviewSchema } from "@/core/config/pair";
|
|
13
14
|
import { getFilenameFromDOM } from "@/core/dom/htmlUtils";
|
|
14
15
|
import { getMarimoCode } from "@/core/meta/globals";
|
|
15
16
|
import {
|
|
@@ -208,6 +209,7 @@ const mountOptionsSchema = z.object({
|
|
|
208
209
|
* marimo config overrides
|
|
209
210
|
*/
|
|
210
211
|
configOverrides: passthroughObject,
|
|
212
|
+
pairPreview: pairPreviewSchema.optional(),
|
|
211
213
|
/**
|
|
212
214
|
* marimo app config
|
|
213
215
|
*/
|
|
@@ -345,6 +347,7 @@ function initStore(options: unknown) {
|
|
|
345
347
|
);
|
|
346
348
|
store.set(userConfigAtom, parseUserConfig(parsedOptions.data.config));
|
|
347
349
|
store.set(appConfigAtom, parseAppConfig(parsedOptions.data.appConfig));
|
|
350
|
+
store.set(pairPreviewAtom, parsedOptions.data.pairPreview);
|
|
348
351
|
|
|
349
352
|
// Runtime config
|
|
350
353
|
if (parsedOptions.data.runtimeConfig.length > 0) {
|