# Instagrow Instagram Scheduling

Canonical: https://vibegrow.io/skills/instagrow
Markdown: https://vibegrow.io/skills/instagrow.md

Use when scheduling, publishing, drafting, or verifying Instagram posts with the Vibegrow Instagrow Chrome extension and Pi Chrome.

## Skill Metadata

- Slug: instagrow
- Category: Research
- Free teaser: no
- Supported agents shown on site: claude-code, cursor, codex, windsurf





## Full Skill Source

# Instagrow Instagram Scheduling

Use this skill to schedule Instagram posts through **Instagrow Post Assistant** in the user's real signed-in Chrome profile. The goal is to create a scheduled Instagrow draft, verify it in Instagrow's queue, and avoid accidentally publishing through Instagram's native Share button.

## Core rule

Use **Instagrow / Post Assistant**, not Instagram's default composer, for scheduling.

Do not click native Instagram **Share** unless the user explicitly asks to publish immediately. Scheduling should end with a visible Instagrow queue item such as `Scheduled Today, 19:45`.

## Required setup

### 1. Pi Chrome access

Pi Chrome lets the agent control the user's existing signed-in Chrome profile.

If chrome tools are unavailable or locked, ask the user to run:

```bash
pi install npm:pi-chrome
/chrome onboard
/chrome doctor
/chrome authorize indefinite
```

Install flow from `https://pi.dev/packages/pi-chrome`:

1. Run `pi install npm:pi-chrome`.
2. Run `/chrome onboard` in Pi.
3. Load the bundled Chrome connector extension in `chrome://extensions`:
   - Enable **Developer mode**.
   - Click **Load unpacked**.
   - Select the `browser-extension/` folder revealed by `/chrome onboard`.
4. Run `/chrome doctor` and confirm the companion extension responds.
5. Run `/chrome authorize indefinite` for long scheduling sessions.

Useful commands:

```bash
/chrome status
/chrome background on      # default, work silently
/chrome background off     # user can watch
/chrome revoke             # lock access again when done
```

Security note: Pi Chrome controls the user's real signed-in Chrome profile only after explicit authorization. Prefer `/chrome revoke` after sensitive workflows are done.

### 2. Instagrow Chrome extension

Install Instagrow from `https://vibegrow.io/instagrow`:

1. Open `https://vibegrow.io/instagrow`.
2. Click **Download Chrome ZIP** for the current release.
3. Unzip the folder and keep it somewhere stable.
4. Open `chrome://extensions`.
5. Enable **Developer mode**.
6. Click **Load unpacked**.
7. Select the unzipped Instagrow extension folder.
8. Open `https://www.instagram.com/` in the same Chrome profile.
9. Verify Instagrow is present: the page should expose a right-side **Post Assistant** panel or a left-nav **Assistant+ / Instagrow** control.

The extension runs in the browser session. Do not ask the user for their Instagram password.

## Scheduling workflow

### 1. Confirm inputs

Before scheduling, confirm:

- Instagram account, e.g. `vibegrow.io`.
- Asset path and format.
- Caption text.
- Schedule date and local time.
- Time zone. If the user says CET during summer in Germany, interpret it as local Berlin time unless they specify otherwise. June is usually **CEST**.

Check local time when needed:

```bash
TZ="Europe/Berlin" date "+%Y-%m-%d %H:%M %Z (%A)"
```

Instagrow has a minimum schedule lead time of about 5 minutes. If the requested time is already past or too soon, ask for a new time.

### 2. Prepare the asset

Instagram/Instagrow usually accepts JPEG reliably for feed images. If the source is PNG, create a JPEG copy while preserving dimensions:

```bash
python3 - <<'PY'
from PIL import Image
from pathlib import Path
src = Path('path/to/post.png')
dst = src.with_suffix('.jpg')
Image.open(src).convert('RGB').save(dst, quality=95, optimize=True, progressive=True)
print(dst)
PY
```

For Instagram feed posts, verify dimensions. A 4:5 post should be `1080x1350`.

### 3. Open Instagram and verify account

Use chrome tools only:

1. `chrome_tab(list)` to find Instagram.
2. `chrome_snapshot` on the Instagram tab.
3. Verify the active/account context is the intended account, usually by checking profile links or Instagrow's selected user label.

Do not switch accounts unless the user asks.

### 4. Prefer direct Instagrow scheduling through its app API

Instagrow exposes its app in the page as `window.$instagrow.app` once loaded. This is the most reliable way to schedule from an agent because the extension UI lives inside Shadow DOM and native file pickers can be brittle.

First verify Instagrow exists:

```js
await window.$instagrow?.app
```

Inspect state if needed:

```js
const app = await window.$instagrow.app;
({
  selectedUserId: app.$s?.later?.selectedUserId,
  posts: app.$later.proxy.getSelectedUserPosts().map(p => ({
    id: p.id,
    status: p.status,
    date: p.date,
    local: p.date ? new Date(p.date).toString() : null,
    type: p.type,
    caption: p.caption?.slice(0, 80),
    mediaCount: p.mediaList?.length,
  }))
})
```

If the local asset must be fetched by the page, serve the directory locally with CORS:

```bash
python3 - <<'PY' > /tmp/instagrow-cors-server.log 2>&1 &
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
import os
ROOT = Path('path/to/asset/folder').resolve()
os.chdir(ROOT)
class Handler(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', '*')
        super().end_headers()
    def do_OPTIONS(self):
        self.send_response(204)
        self.end_headers()
ThreadingHTTPServer(('127.0.0.1', 8765), Handler).serve_forever()
PY
```

Then schedule via `chrome_evaluate`:

```js
(async () => {
  const app = await window.$instagrow.app;
  const caption = `CAPTION_TEXT_HERE`;

  // Local browser time. Month is zero-based: June = 5.
  const scheduledFor = new Date(2026, 5, 2, 19, 45, 0, 0).getTime();

  const res = await fetch('http://127.0.0.1:8765/asset.jpg');
  if (!res.ok) throw new Error(`Failed to fetch asset: ${res.status}`);

  const blob = await res.blob();
  const file = new File([blob], 'asset.jpg', {
    type: 'image/jpeg',
    lastModified: Date.now(),
  });

  const selectedUserId =
    app.$s?.later?.selectedUserId ||
    app.$later?.proxy?.getSelectedUserState?.()?.auth?.userId;

  if (!selectedUserId) throw new Error('No Instagrow selected user/account.');

  const postId = await app.$later.controller.createPost(selectedUserId, [file], {
    caption,
    date: scheduledFor,
    status: 'scheduled',
    type: 'post',
    options: {
      besties: false,
      disableComments: false,
      hideLikes: false,
    },
  });

  app.$s.transaction?.(s => {
    if (!s?.later) return;
    s.later.processing = false;
    s.later.selectedUserId = selectedUserId;
    s.later.selectedPostId = postId;
    s.later.showBodyPanel = true;
    s.later.selectedPillId = null;
    s.later.lastDate = scheduledFor;
  });

  const post = app.$later.proxy.getPost(postId);
  return {
    ok: true,
    postId,
    selectedUserId,
    scheduledFor,
    scheduledForLocal: new Date(scheduledFor).toString(),
    post: {
      id: post?.id,
      status: post?.status,
      date: post?.date,
      caption: post?.caption,
      type: post?.type,
      mediaCount: post?.mediaList?.length,
      media: post?.mediaList?.[0] && {
        width: post.mediaList[0].width,
        height: post.mediaList[0].height,
        isVideo: post.mediaList[0].isVideo,
        fileId: post.mediaList[0].fileId,
        previewId: post.mediaList[0].previewId,
      },
    },
  };
})()
```

Use local time in `new Date(...)`. If the user gives an absolute timezone, convert carefully before scheduling.

### 5. Verify the schedule

Immediately verify through Instagrow state:

```js
(async () => {
  const app = await window.$instagrow.app;
  return app.$later.proxy.getSelectedUserPosts().map(p => ({
    id: p.id,
    status: p.status,
    date: p.date,
    local: p.date ? new Date(p.date).toString() : null,
    type: p.type,
    caption: p.caption,
    mediaCount: p.mediaList?.length,
    width: p.mediaList?.[0]?.width,
    height: p.mediaList?.[0]?.height,
    isVideo: p.mediaList?.[0]?.isVideo,
  }));
})()
```

Confirm:

- `status` is `scheduled`.
- `local` matches the requested date/time.
- `caption` matches exactly.
- `mediaCount` is correct.
- dimensions match expected creative.
- Instagrow side panel visually shows `Scheduled Today, HH:MM` or equivalent.

Take a screenshot:

```text
chrome_screenshot(path: ".pi/chrome-screenshots/<descriptive-name>.png")
```

Report the screenshot path to the user.

## UI-based fallback

If direct scheduling is not appropriate, use Instagrow UI, not native Instagram UI:

1. Open Instagram.
2. Open **Assistant+ / Instagrow Post Assistant**.
3. Use **ADD FILES** inside the Post Assistant panel.
4. Fill caption in the Instagrow editor.
5. Open the **Schedule** tab inside the Instagrow editor.
6. Pick date/time or create a Time Slot, e.g. `19:45`.
7. Save/schedule.
8. Verify the queue item appears in Post Assistant.

If Chrome snapshots do not expose controls inside Instagrow, inspect the Shadow DOM:

```js
const sh = document.querySelector('#shadow')?.shadowRoot;
sh?.textContent
```

Clickable controls can be found with:

```js
[...document.querySelector('#shadow').shadowRoot.querySelectorAll('button,input,textarea,[role=button]')]
  .map(el => ({
    text: (el.innerText || el.textContent || el.value || '').trim(),
    tag: el.tagName,
    rect: el.getBoundingClientRect().toJSON?.() || el.getBoundingClientRect(),
  }))
```

## Mistakes to avoid

- Do **not** use Instagram native Create/Share for scheduled posts.
- Do **not** click **Share** unless the user explicitly asked to publish now.
- Do **not** use OS-level automation, AppleScript, QuickTime screenshots, or manual file dialogs. Use `chrome_*` tools.
- Do **not** assume `chrome_upload_file` will work against Instagrow's Shadow DOM or hidden extension inputs. Prefer direct Instagrow app scheduling.
- Do **not** schedule before verifying the selected Instagram account.
- Do **not** leave a native Instagram composer draft open if it was created accidentally. Close/discard it after confirming the Instagrow scheduled draft exists.
- Do **not** silently convert time zones. Tell the user if CET/CEST ambiguity matters.

## Final response format

Keep the final confirmation concise:

```text
Done. Scheduled through Instagrow Post Assistant for @account.

- Status: Scheduled
- Time: Today, 19:45 CEST
- Creative: path/to/file.jpg
- Caption: final approved caption
- Verification screenshot: .pi/chrome-screenshots/name.png
```
