feat(instagram): add post, reel, story, and note publishing#671
feat(instagram): add post, reel, story, and note publishing#671Zezi-Ray wants to merge 10 commits intojackwener:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Expands opencli’s Instagram automation surface area (feed posts, reels, stories, notes) and adds Browser Bridge capabilities needed for richer publishing flows (native file input, network capture, native text insertion).
Changes:
- Added new Instagram CLI commands:
instagram post --media ...,instagram reel --video ...,instagram story --media ..., andinstagram note "..."with corresponding adapter tests. - Introduced CLI arg enhancements (
valueRequired) and a command-levelvalidateArgshook wired into execution and Commander parsing. - Added Browser Bridge protocol + implementation support for
insert-textand network capture start/read, enabling private-route publishing helpers to extract needed request context.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types.ts | Extends IPage with optional network capture + native insertText capabilities. |
| src/serialization.ts | Serializes new valueRequired arg metadata. |
| src/registry.ts | Adds valueRequired for args and validateArgs for commands. |
| src/execution.ts | Runs cmd.validateArgs after coercion/validation and before any execution. |
| src/commanderAdapter.ts | Updates option syntax based on valueRequired and invokes validateArgs in adapter flow. |
| src/commanderAdapter.test.ts | Adds coverage for “optional option but value required” behavior and early validation. |
| src/clis/instagram/story.ts | Implements instagram story --media ... via private publish route with URL resolution. |
| src/clis/instagram/story.test.ts | Adds unit tests for story arg validation + private publish integration. |
| src/clis/instagram/reel.ts | Implements instagram reel --video ... UI automation flow with upload + caption handling. |
| src/clis/instagram/reel.test.ts | Adds unit tests for reel flow (upload, caption, safe filename handling). |
| src/clis/instagram/post.ts | Implements instagram post --media ... with private-first publish + UI fallback and protocol capture. |
| src/clis/instagram/note.ts | Implements instagram note publishing via web inbox GraphQL mutation. |
| src/clis/instagram/note.test.ts | Adds unit tests for note validation and publish happy-path. |
| src/clis/instagram/_shared/runtime-info.ts | Adds runtime info extraction helpers (appId/csrf/ajax) used by private routes. |
| src/clis/instagram/_shared/protocol-capture.ts | Adds protocol/network capture utilities and instagramPrivateApiFetch. |
| src/clis/instagram/_shared/protocol-capture.test.ts | Adds tests for capture installation/read + private fetch behavior. |
| src/clis/instagram/_shared/private-publish.ts | Adds private-route publishing implementation (feed, sidecar, story) and supporting media prep. |
| src/clis/instagram/_shared/private-publish.test.ts | Adds extensive tests for private publish helpers (payloads, retries, story publishing). |
| src/build-manifest.ts | Emits valueRequired into the build-time CLI manifest. |
| src/browser/page.ts | Implements startNetworkCapture, readNetworkCapture, and insertText via daemon commands. |
| src/browser/daemon-client.ts | Extends daemon command union with insert-text + network capture actions/params. |
| extension/src/protocol.ts | Extends extension protocol to include insert-text and network capture actions. |
| extension/src/cdp.ts | Implements CDP Input.insertText and CDP-driven network capture buffering. |
| extension/src/background.ts | Routes new actions to CDP executor: insert-text + network capture start/read. |
| extension/dist/background.js | Updates compiled extension artifact to include new actions and adjusted reconnect behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const v = optionsRecord[arg.name] ?? optionsRecord[camelName]; | ||
| if (v !== undefined) kwargs[arg.name] = normalizeArgValue(arg.type, v, arg.name); | ||
| } | ||
| cmd.validateArgs?.(kwargs); |
There was a problem hiding this comment.
cmd.validateArgs is invoked here and also inside executeCommand() (src/execution.ts:158-160). This causes validation to run twice on every Commander invocation and can change error typing/formatting (errors thrown here won’t be wrapped into ArgumentError like they are in executeCommand). Consider removing this call and relying on executeCommand’s early validation (it runs before any browser session work), or ensure validation is only performed in one place consistently.
| cmd.validateArgs?.(kwargs); |
| function validateVideoPath(input: unknown): string { | ||
| const resolved = path.resolve(String(input || '').trim()); | ||
| if (!resolved) { | ||
| throw new ArgumentError('Video path cannot be empty'); | ||
| } | ||
| if (!fs.existsSync(resolved)) { | ||
| throw new ArgumentError(`Video file not found: ${resolved}`); | ||
| } | ||
| const ext = path.extname(resolved).toLowerCase(); | ||
| if (!SUPPORTED_VIDEO_EXTENSIONS.has(ext)) { | ||
| throw new ArgumentError(`Unsupported video format: ${ext}`, 'Supported formats: .mp4'); | ||
| } | ||
| return resolved; |
There was a problem hiding this comment.
validateVideoPath() calls path.resolve() on an empty/blank input, but path.resolve('') returns the current working directory. This means an empty --video ""/blank value won’t hit the "cannot be empty" branch and can produce misleading errors (or pass existsSync on a directory). Consider checking the trimmed raw string before path.resolve, and also verify the resolved path is a file (e.g., via statSync().isFile()).
| function buildSafeTempVideoPath(filePath: string): string { | ||
| const ext = path.extname(filePath).toLowerCase() || '.mp4'; | ||
| return path.join(os.tmpdir(), `opencli-instagram-video-real${ext}`); | ||
| } |
There was a problem hiding this comment.
buildSafeTempVideoPath() returns a fixed filename in the OS temp directory. Parallel executions (or retries) can clobber each other’s temp uploads and/or upload the wrong file. Use a unique name per invocation (e.g., include crypto.randomUUID()/Date.now() + random suffix) and consider preserving the original basename for easier debugging.
| for (let attempt = 0; attempt < INSTAGRAM_PRIVATE_CONFIG_RETRY_BUDGET; attempt += 1) { | ||
| try { | ||
| if (typeof page.startNetworkCapture === 'function') { | ||
| await page.startNetworkCapture(INSTAGRAM_PRIVATE_CAPTURE_PATTERN); | ||
| } | ||
| await page.goto(`${INSTAGRAM_HOME_URL}?__opencli_private_probe=${Date.now()}`); | ||
| await page.wait({ time: 2 }); | ||
|
|
||
| const [cookies, runtime, entries] = await Promise.all([ | ||
| page.getCookies({ domain: 'instagram.com' }), | ||
| page.evaluate(buildReadInstagramRuntimeInfoJs()) as Promise<InstagramRuntimeInfo>, | ||
| typeof page.readNetworkCapture === 'function' | ||
| ? page.readNetworkCapture() as Promise<unknown[]> | ||
| : Promise.resolve([]), | ||
| ]); | ||
|
|
There was a problem hiding this comment.
resolveInstagramPrivatePublishConfig() starts native network capture for every attempt using a very broad pattern (/api/v1/|/graphql/). Given the extension capture currently records request/response previews, this can collect and transmit a lot of data (including potentially sensitive payload snippets) even though only a few headers are needed to derive context. Consider narrowing the capture pattern to the minimal endpoints needed for header discovery, and/or disabling body/response preview capture unless an explicit debug/capture flag is enabled.
Summary
Expanded Instagram publishing support related to #626.
Status
This PR is now ready for review.
Included in this branch
instagram post --media ...as the unified feed-post entrypointinstagram reel --video ...for Reel publishinginstagram story --media ...for single image/video Story publishinginstagram note "..."using the real desktop web inbox mutationCurrent behavior
instagram postsupports:instagram reelsupports single-video Reel publishinginstagram storysupports single image or single video Story publishinginstagram notesupports publishing a short text NoteVerification
npx vitest run src/clis/instagram/post.test.ts src/clis/instagram/_shared/private-publish.test.ts src/clis/instagram/reel.test.ts src/clis/instagram/story.test.ts src/clis/instagram/note.test.tsnpm run typechecknpm run buildIssue
Refs #626