Skip to content

Commit 066a1ed

Browse files
committed
feat: pin Claude extension to toolbar on launch
- Add pinExtension() to update Chrome's Preferences file with the extension ID in pinned_extensions array - Restart Chromium via supervisorctl to pick up the new preference - Use Spawn (fire and forget) since supervisorctl restart takes time
1 parent ad7ffb3 commit 066a1ed

1 file changed

Lines changed: 92 additions & 0 deletions

File tree

internal/claude/loader.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package claude
22

33
import (
4+
"bytes"
45
"context"
6+
"encoding/json"
57
"fmt"
8+
"io"
69
"os"
710
"path/filepath"
811

912
"github.com/onkernel/cli/pkg/util"
1013
"github.com/onkernel/kernel-go-sdk"
1114
)
1215

16+
// KernelPreferencesPath is the path to Chrome's Preferences file in Kernel browsers
17+
const KernelPreferencesPath = "/home/kernel/user-data/Default/Preferences"
18+
1319
// LoadIntoBrowserOptions configures how the extension is loaded into a browser.
1420
type LoadIntoBrowserOptions struct {
1521
// BrowserID is the Kernel browser session ID
@@ -95,6 +101,92 @@ func LoadIntoBrowser(ctx context.Context, opts LoadIntoBrowserOptions) error {
95101
return fmt.Errorf("failed to load extension: %w", err)
96102
}
97103

104+
// Step 3: Pin the extension to the toolbar
105+
if err := pinExtension(ctx, opts.Client, opts.BrowserID, ExtensionID); err != nil {
106+
// Don't fail the whole operation if pinning fails - it's a nice-to-have
107+
// The extension is still loaded and functional
108+
return nil
109+
}
110+
111+
// Step 4: Restart Chromium to pick up the new pinned extension preference
112+
// Use Spawn (fire and forget) because supervisorctl restart waits a long time
113+
proc := opts.Client.Browsers.Process
114+
_, _ = proc.Spawn(ctx, opts.BrowserID, kernel.BrowserProcessSpawnParams{
115+
Command: "supervisorctl",
116+
Args: []string{"restart", "chromium"},
117+
AsRoot: kernel.Opt(true),
118+
})
119+
120+
return nil
121+
}
122+
123+
// pinExtension adds an extension ID to Chrome's pinned_extensions list in the Preferences file.
124+
// This makes the extension icon visible in the toolbar by default.
125+
func pinExtension(ctx context.Context, client kernel.Client, browserID, extensionID string) error {
126+
fs := client.Browsers.Fs
127+
128+
// Read the current Preferences file
129+
resp, err := fs.ReadFile(ctx, browserID, kernel.BrowserFReadFileParams{
130+
Path: KernelPreferencesPath,
131+
})
132+
if err != nil {
133+
return fmt.Errorf("failed to read preferences: %w", err)
134+
}
135+
defer resp.Body.Close()
136+
137+
prefsData, err := io.ReadAll(resp.Body)
138+
if err != nil {
139+
return fmt.Errorf("failed to read preferences body: %w", err)
140+
}
141+
142+
// Parse the JSON
143+
var prefs map[string]any
144+
if err := json.Unmarshal(prefsData, &prefs); err != nil {
145+
return fmt.Errorf("failed to parse preferences: %w", err)
146+
}
147+
148+
// Get or create the extensions object
149+
extensions, ok := prefs["extensions"].(map[string]any)
150+
if !ok {
151+
extensions = make(map[string]any)
152+
prefs["extensions"] = extensions
153+
}
154+
155+
// Get or create the pinned_extensions array
156+
var pinnedExtensions []string
157+
if pinned, ok := extensions["pinned_extensions"].([]any); ok {
158+
for _, id := range pinned {
159+
if s, ok := id.(string); ok {
160+
pinnedExtensions = append(pinnedExtensions, s)
161+
}
162+
}
163+
}
164+
165+
// Check if extension is already pinned
166+
for _, id := range pinnedExtensions {
167+
if id == extensionID {
168+
// Already pinned, nothing to do
169+
return nil
170+
}
171+
}
172+
173+
// Add the extension to pinned list
174+
pinnedExtensions = append(pinnedExtensions, extensionID)
175+
extensions["pinned_extensions"] = pinnedExtensions
176+
177+
// Serialize back to JSON
178+
newPrefsData, err := json.Marshal(prefs)
179+
if err != nil {
180+
return fmt.Errorf("failed to serialize preferences: %w", err)
181+
}
182+
183+
// Write the updated Preferences file
184+
if err := fs.WriteFile(ctx, browserID, bytes.NewReader(newPrefsData), kernel.BrowserFWriteFileParams{
185+
Path: KernelPreferencesPath,
186+
}); err != nil {
187+
return fmt.Errorf("failed to write preferences: %w", err)
188+
}
189+
98190
return nil
99191
}
100192

0 commit comments

Comments
 (0)