@marimo-team/islands 0.22.1-dev37 → 0.22.1-dev38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.22.1-dev37",
3
+ "version": "0.22.1-dev38",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,142 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { useAtomValue } from "jotai";
4
+ import { CheckIcon, CopyIcon } from "lucide-react";
5
+ import React, { useState } from "react";
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from "@/components/ui/dialog";
14
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
15
+ import { runtimeConfigAtom } from "@/core/runtime/config";
16
+ import { copyToClipboard } from "@/utils/copy";
17
+ import { Events } from "@/utils/events";
18
+ import { Tooltip } from "@/components/ui/tooltip";
19
+ import type { RuntimeConfig } from "@/core/runtime/types";
20
+
21
+ type AgentTab = "claude" | "codex" | "opencode";
22
+
23
+ function buildRemoteUrl(config: RuntimeConfig) {
24
+ const url = new URL(config.url);
25
+ if (config.authToken) {
26
+ url.searchParams.set("auth", config.authToken);
27
+ }
28
+ return url.toString();
29
+ }
30
+
31
+ function getPromptCommand(agent: AgentTab, remoteUrl: string): string {
32
+ const command = import.meta.env.DEV ? "uv run marimo" : "uvx marimo@latest";
33
+ const base = `${command} pair prompt --url '${remoteUrl}'`;
34
+ switch (agent) {
35
+ case "claude":
36
+ return `claude "$(${base} --claude)"`;
37
+ case "codex":
38
+ return `codex "$(${base} --codex)"`;
39
+ case "opencode":
40
+ return `opencode "$(${base} --opencode)"`;
41
+ }
42
+ }
43
+
44
+ const SKILL_INSTALL = "npx skills add marimo-team/marimo-pair";
45
+
46
+ export const PairWithAgentModal: React.FC<{
47
+ onClose: () => void;
48
+ }> = ({ onClose }) => {
49
+ const [activeTab, setActiveTab] = useState<AgentTab>("claude");
50
+ const runtimeConfig = useAtomValue(runtimeConfigAtom);
51
+ const remoteUrl = buildRemoteUrl(runtimeConfig);
52
+ const promptCommand = getPromptCommand(activeTab, remoteUrl);
53
+
54
+ return (
55
+ <DialogContent className="sm:max-w-lg">
56
+ <DialogHeader>
57
+ <DialogTitle>Pair with an agent</DialogTitle>
58
+ <DialogDescription>
59
+ Use an AI coding agent to pair-program on this notebook.{" "}
60
+ <a
61
+ href="https://links.marimo.app/marimo-pair"
62
+ target="_blank"
63
+ rel="noopener noreferrer"
64
+ className="underline"
65
+ >
66
+ Learn more
67
+ </a>
68
+ .
69
+ </DialogDescription>
70
+ </DialogHeader>
71
+
72
+ <div className="flex flex-col gap-4 py-2">
73
+ <div className="flex flex-col gap-2">
74
+ <span className="text-sm font-medium">1. Install the skill</span>
75
+ <CommandBlock command={SKILL_INSTALL} />
76
+ </div>
77
+
78
+ <div className="flex flex-col gap-2">
79
+ <span className="text-sm font-medium">2. Run in your terminal</span>
80
+ <Tabs
81
+ value={activeTab}
82
+ onValueChange={(v) => setActiveTab(v as AgentTab)}
83
+ >
84
+ <TabsList className="w-full">
85
+ <TabsTrigger value="claude" className="flex-1">
86
+ Claude
87
+ </TabsTrigger>
88
+ <TabsTrigger value="codex" className="flex-1">
89
+ Codex
90
+ </TabsTrigger>
91
+ <TabsTrigger value="opencode" className="flex-1">
92
+ OpenCode
93
+ </TabsTrigger>
94
+ </TabsList>
95
+
96
+ <TabsContent value="claude" className="mt-3">
97
+ <CommandBlock command={promptCommand} />
98
+ </TabsContent>
99
+ <TabsContent value="codex" className="mt-3">
100
+ <CommandBlock command={promptCommand} />
101
+ </TabsContent>
102
+ <TabsContent value="opencode" className="mt-3">
103
+ <CommandBlock command={promptCommand} />
104
+ </TabsContent>
105
+ </Tabs>
106
+ </div>
107
+ </div>
108
+
109
+ <DialogFooter>
110
+ <Button variant="secondary" onClick={onClose}>
111
+ Close
112
+ </Button>
113
+ </DialogFooter>
114
+ </DialogContent>
115
+ );
116
+ };
117
+
118
+ const CommandBlock: React.FC<{ command: string }> = ({ command }) => {
119
+ const [copied, setCopied] = useState(false);
120
+
121
+ const copy = Events.stopPropagation(async (e) => {
122
+ e.preventDefault();
123
+ await copyToClipboard(command);
124
+ setCopied(true);
125
+ setTimeout(() => setCopied(false), 2000);
126
+ });
127
+
128
+ return (
129
+ <div className="flex items-center gap-2 rounded-md bg-muted px-3 py-2 font-mono text-xs">
130
+ <code className="flex-1 select-all break-words">{command}</code>
131
+ <Tooltip content="Copied!" open={copied}>
132
+ <Button onClick={copy} size="xs" variant="ghost">
133
+ {copied ? (
134
+ <CheckIcon size={14} strokeWidth={1.5} />
135
+ ) : (
136
+ <CopyIcon size={14} strokeWidth={1.5} />
137
+ )}
138
+ </Button>
139
+ </Tooltip>
140
+ </div>
141
+ );
142
+ };
@@ -36,6 +36,7 @@ import {
36
36
  PresentationIcon,
37
37
  SettingsIcon,
38
38
  Share2Icon,
39
+ SparklesIcon,
39
40
  Undo2Icon,
40
41
  XCircleIcon,
41
42
  YoutubeIcon,
@@ -45,6 +46,7 @@ import { settingDialogAtom } from "@/components/app-config/state";
45
46
  import { MarkdownIcon } from "@/components/editor/cell/code/icons";
46
47
  import { useImperativeModal } from "@/components/modal/ImperativeModal";
47
48
  import { renderShortcut } from "@/components/shortcuts/renderShortcut";
49
+ import { PairWithAgentModal } from "@/components/editor/actions/pair-with-agent-modal";
48
50
  import { ShareStaticNotebookModal } from "@/components/static-html/share-modal";
49
51
  import { toast } from "@/components/ui/use-toast";
50
52
  import {
@@ -343,6 +345,14 @@ export function useNotebookActions() {
343
345
  ],
344
346
  },
345
347
 
348
+ {
349
+ icon: <SparklesIcon size={14} strokeWidth={1.5} />,
350
+ label: "Pair with an agent",
351
+ handle: async () => {
352
+ openModal(<PairWithAgentModal onClose={closeModal} />);
353
+ },
354
+ },
355
+
346
356
  {
347
357
  icon: <Share2Icon size={14} strokeWidth={1.5} />,
348
358
  label: "Share",