Files
jackwener__opencli/clis/chatgpt/send.js
pi-dal 19e64da565 feat(chatgpt): add project commands (#1992)
* feat(chatgpt): add project commands

Add ChatGPT project management adapters for listing visible projects and uploading local files into project knowledge.

The new project-list command extracts project links from stable sidebar anchors first, with a React Fiber fallback for sidebar builds that do not expose hrefs. The project-file-add command uploads through the project knowledge flow, validates local files before browser interaction, and waits for filename confirmation before reporting success.

For the current ChatGPT project UI, project knowledge uploads live behind the Sources tab rather than the older Add files dialog. The upload helper now prefers that Sources surface, avoids mistaking the chat composer plus button for project knowledge upload, and dispatches a browser-like pointer/mouse sequence so Radix-powered tabs activate reliably in live sessions before setting the source file input.

This intentionally avoids command-level system proxy mutation. Users who need a proxy should configure the browser or network environment outside this adapter, rather than letting a single command toggle OS proxy settings.

Also update the generated CLI manifest and focused adapter tests for command registration, argument contracts, project id parsing, project link extraction, upload confirmation, live Sources-tab upload behavior, and failure wrapping.

Validation:
- pnpm exec tsx src/main.ts chatgpt project-list -f json --trace retain-on-failure --window foreground --keep-tab true (live authenticated local session; returned visible projects)
- pnpm exec tsx src/main.ts chatgpt project-file-add /tmp/opencli-chatgpt-project-upload-validation-pointer-20260621215147.txt --id 6a1791df8fa88191afb5a016ce1f497e -f json --trace retain-on-failure --window foreground --keep-tab true (live authenticated local session; uploaded one text file to project knowledge)
- pnpm exec vitest run --project adapter clis/chatgpt/commands.test.js clis/chatgpt/envelope.test.js clis/chatgpt/image.test.js clis/chatgpt/model.test.js clis/chatgpt/utils.test.js
- pnpm exec tsc --noEmit
- pnpm run build-manifest

* feat(chatgpt): support project chat routing

Add --project to chatgpt ask/send so messages can start a new chat inside a specified ChatGPT project. Reject --project with --conversation before navigation, and parse project-scoped /g/g-p-.../c/<id> conversation URLs so ask can report the created conversation id.

Validation:

- pnpm exec vitest run --project adapter clis/chatgpt/commands.test.js clis/chatgpt/envelope.test.js clis/chatgpt/image.test.js clis/chatgpt/model.test.js clis/chatgpt/utils.test.js

- pnpm exec tsc --noEmit

- pnpm run build-manifest

* feat(chatgpt): extend project routing

Add --project routing to chatgpt new, image, and model so project-scoped work is available beyond ask/send. New and image open the specified project before preparing the composer; model opens the project before switching the intelligence level.

Validation:

- pnpm exec vitest run --project adapter clis/chatgpt/commands.test.js clis/chatgpt/envelope.test.js clis/chatgpt/image.test.js clis/chatgpt/model.test.js clis/chatgpt/utils.test.js

- pnpm exec tsc --noEmit

- pnpm run build-manifest

- pnpm exec tsx src/main.ts chatgpt new --help / image --help / model --help

* fix(chatgpt): harden project command boundaries

* fix(chatgpt): require stable project ids

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-06-22 16:26:59 +08:00

69 lines
2.8 KiB
JavaScript

import { cli, Strategy } from '@jackwener/opencli/registry';
import { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';
import {
CHATGPT_DOMAIN,
CHATGPT_URL,
ensureChatGPTComposer,
ensureOnChatGPT,
normalizeBooleanFlag,
openChatGPTConversation,
requireNonEmptyPrompt,
sendChatGPTMessage,
startNewChat,
navigateToProject,
} from './utils.js';
export const sendCommand = cli({
site: 'chatgpt',
name: 'send',
access: 'write',
description: 'Send a prompt to ChatGPT web without waiting for the response',
domain: CHATGPT_DOMAIN,
strategy: Strategy.COOKIE,
browser: true,
siteSession: 'persistent',
navigateBefore: false,
args: [
{ name: 'prompt', positional: true, required: true, help: 'Prompt to send' },
{ name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
{ name: 'conversation', valueRequired: true, help: 'Continue an existing ChatGPT conversation ID or /c/<id> URL' },
{ name: 'project', valueRequired: true, help: 'Start a new chat inside a ChatGPT project ID or /g/g-p-<id> URL' },
],
columns: ['Status', 'InjectedText'],
func: async (page, kwargs) => {
const prompt = requireNonEmptyPrompt(kwargs.prompt, 'chatgpt send');
if (normalizeBooleanFlag(kwargs.new) && kwargs.conversation) {
throw new ArgumentError(
'chatgpt send cannot use --new and --conversation together',
'Choose either a new chat or an existing conversation.',
);
}
if (kwargs.project && kwargs.conversation) {
throw new ArgumentError(
'chatgpt send cannot use --project and --conversation together',
'Choose either a project new chat or an existing conversation.',
);
}
if (kwargs.conversation) {
await openChatGPTConversation(page, kwargs.conversation);
} else if (kwargs.project) {
await navigateToProject(page, kwargs.project);
} else if (normalizeBooleanFlag(kwargs.new)) {
await startNewChat(page);
} else {
await ensureOnChatGPT(page);
}
// startNewChat / ensureOnChatGPT now wait for the composer selector
// after navigating, so the previous standalone 2 s settle is redundant.
await ensureChatGPTComposer(page, 'ChatGPT send requires a logged-in ChatGPT session with a visible composer.');
const sent = await sendChatGPTMessage(page, prompt);
if (!sent) {
throw new CommandExecutionError('Failed to send message to ChatGPT', `Open ${CHATGPT_URL} and verify the composer is ready.`);
}
return [{ Status: 'Success', InjectedText: prompt }];
},
});