@youdie006/prodex 0.29.0 → 0.31.0
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/dist/chatgpt-browser.js +14 -0
- package/dist/cli-pro.js +8 -1
- package/dist/cli.js +8 -0
- package/dist/tui-flow.js +18 -0
- package/dist/tui-run.js +38 -5
- package/dist/tui.js +49 -1
- package/package.json +1 -1
package/dist/chatgpt-browser.js
CHANGED
|
@@ -2502,6 +2502,20 @@ export async function navigateChatGptTabTo(url, options = {}) {
|
|
|
2502
2502
|
cdp.close();
|
|
2503
2503
|
}
|
|
2504
2504
|
}
|
|
2505
|
+
/** Projects with the id their conversations are tagged with. */
|
|
2506
|
+
export async function listChatGptProjectsWithIds(input = {}) {
|
|
2507
|
+
const port = resolveCdpPort(input.port);
|
|
2508
|
+
const page = await findChatGptPage(port, input.timeoutMs ?? 3_000);
|
|
2509
|
+
if (!page.ok || !page.page)
|
|
2510
|
+
return [];
|
|
2511
|
+
const { projectsWithIdsExpression } = await import("./tui.js");
|
|
2512
|
+
try {
|
|
2513
|
+
return (await evaluateOnPage(page.page, projectsWithIdsExpression(), { timeoutMs: 30_000 })) ?? [];
|
|
2514
|
+
}
|
|
2515
|
+
catch {
|
|
2516
|
+
return [];
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2505
2519
|
export async function listRecentChatGptConversations(input = {}) {
|
|
2506
2520
|
const port = resolveCdpPort(input.port);
|
|
2507
2521
|
const page = await findChatGptPage(port, input.timeoutMs ?? 3_000);
|
package/dist/cli-pro.js
CHANGED
|
@@ -838,7 +838,14 @@ export async function runAskProCommand(rest, io) {
|
|
|
838
838
|
// reasoning axis explicitly suppresses the default for the other axis, and
|
|
839
839
|
// pinning --target-url suppresses a default project (it would navigate away
|
|
840
840
|
// from the confirmed tab).
|
|
841
|
-
|
|
841
|
+
// Choosing an effort IS choosing the reasoning axis, and ChatGPT deselects
|
|
842
|
+
// Pro the moment an effort is set - so applying a pinned Pro first would
|
|
843
|
+
// select a model only to undo it, and a quick question would still pay for
|
|
844
|
+
// the Pro selection dance.
|
|
845
|
+
// --pro-mode refines Pro, so it keeps a pinned Pro. --effort replaces it:
|
|
846
|
+
// ChatGPT deselects Pro the moment an effort is set, so applying the pinned
|
|
847
|
+
// Pro first would select a model only to undo it.
|
|
848
|
+
const selectionModel = explicitModel ?? (explicitEffort !== undefined ? undefined : browserDefaults?.model);
|
|
842
849
|
const selectionProjectNew = explicitProjectNew;
|
|
843
850
|
// A persisted default project APPLIES under --new-chat: since 0.16.11 a
|
|
844
851
|
// fresh chat inside the project is exactly what "--new-chat + project"
|
package/dist/cli.js
CHANGED
|
@@ -74,6 +74,10 @@ async function runInteractiveUi(io) {
|
|
|
74
74
|
const { loadBrowserDefaults } = await import("./config.js");
|
|
75
75
|
return (await loadBrowserDefaults(io.cwd).catch(() => undefined))?.project;
|
|
76
76
|
},
|
|
77
|
+
pinnedModel: async () => {
|
|
78
|
+
const { loadBrowserDefaults } = await import("./config.js");
|
|
79
|
+
return (await loadBrowserDefaults(io.cwd).catch(() => undefined))?.model;
|
|
80
|
+
},
|
|
77
81
|
listConversations: async () => {
|
|
78
82
|
const { listRecentChatGptConversations } = await import("./chatgpt-browser.js");
|
|
79
83
|
return listRecentChatGptConversations({});
|
|
@@ -82,6 +86,10 @@ async function runInteractiveUi(io) {
|
|
|
82
86
|
const { navigateChatGptTabTo } = await import("./chatgpt-browser.js");
|
|
83
87
|
return navigateChatGptTabTo(url, {});
|
|
84
88
|
},
|
|
89
|
+
listProjectsWithIds: async () => {
|
|
90
|
+
const { listChatGptProjectsWithIds } = await import("./chatgpt-browser.js");
|
|
91
|
+
return listChatGptProjectsWithIds({});
|
|
92
|
+
},
|
|
85
93
|
listProjects: async () => {
|
|
86
94
|
const { listChatGptSidebarProjects } = await import("./chatgpt-browser.js");
|
|
87
95
|
const listed = await listChatGptSidebarProjects({});
|
package/dist/tui-flow.js
CHANGED
|
@@ -32,3 +32,21 @@ export function destinationChoices(pinnedProject) {
|
|
|
32
32
|
...(pinnedProject ? [{ id: "no-project", label: "New chat, ignoring the pinned project" }] : [])
|
|
33
33
|
];
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* prodex is named for Pro, but the same signed-in browser runs the ordinary
|
|
37
|
+
* reasoning levels, and a one-line question does not want minutes of Pro.
|
|
38
|
+
* Offered only for an ordinary chat: the tool kinds bring their own pipeline,
|
|
39
|
+
* and ChatGPT deselects Pro as soon as an effort is set.
|
|
40
|
+
*/
|
|
41
|
+
export function effortChoices(pinnedModel) {
|
|
42
|
+
// Reads down from what the repo pinned: the level just under Pro sits just
|
|
43
|
+
// under "Keep", and Instant is at the bottom. Climbing up from Instant put
|
|
44
|
+
// the levels closest to Pro furthest from it.
|
|
45
|
+
return [
|
|
46
|
+
{ label: pinnedModel ? `Keep ${pinnedModel}` : "Keep the current selection", hint: "whatever this repo pinned" },
|
|
47
|
+
{ effort: "max", label: "Extra high", hint: "strongest of the standard levels" },
|
|
48
|
+
{ effort: "high", label: "High" },
|
|
49
|
+
{ effort: "medium", label: "Medium" },
|
|
50
|
+
{ effort: "instant", label: "Instant", hint: "seconds, for a quick question" }
|
|
51
|
+
];
|
|
52
|
+
}
|
package/dist/tui-run.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import readline from "node:readline";
|
|
10
10
|
import { renderBanner } from "./banner.js";
|
|
11
|
-
import { destinationChoices, SEND_KINDS } from "./tui-flow.js";
|
|
12
|
-
import { consultArgsFromChoices, conversationThreadUrl, moveCursor, progressLabel, renderContextPanel, renderProgressBar, renderSelectList } from "./tui.js";
|
|
11
|
+
import { destinationChoices, effortChoices, SEND_KINDS } from "./tui-flow.js";
|
|
12
|
+
import { consultArgsFromChoices, conversationsInProject, conversationThreadUrl, moveCursor, progressLabel, renderContextPanel, renderProgressBar, renderSelectList } from "./tui.js";
|
|
13
13
|
const ESC = "";
|
|
14
14
|
const CLEAR = `${ESC}[2J${ESC}[H`;
|
|
15
15
|
// The alternate screen buffer: the terminal comes back exactly as it was, so a
|
|
@@ -152,6 +152,7 @@ export async function runInteractiveConsult(io, deps) {
|
|
|
152
152
|
// being read, so no step waits on the network.
|
|
153
153
|
const contextPromise = deps.describeContext?.().catch(() => []);
|
|
154
154
|
const pinnedPromise = deps.pinnedProject?.().catch(() => undefined);
|
|
155
|
+
const pinnedModelPromise = deps.pinnedModel?.().catch(() => undefined);
|
|
155
156
|
const conversationsPromise = deps.listConversations?.().catch(() => []);
|
|
156
157
|
const kindChoice = await pick(io, {
|
|
157
158
|
title: "What kind of send is this?",
|
|
@@ -161,6 +162,19 @@ export async function runInteractiveConsult(io, deps) {
|
|
|
161
162
|
if (kindChoice === undefined)
|
|
162
163
|
return cancel(io);
|
|
163
164
|
const tools = SEND_KINDS[kindChoice].tools;
|
|
165
|
+
// Only an ordinary chat has a reasoning level to choose: the tool kinds
|
|
166
|
+
// bring their own pipeline, and an effort would deselect Pro under them.
|
|
167
|
+
let effort;
|
|
168
|
+
if (SEND_KINDS[kindChoice].id === "chat") {
|
|
169
|
+
const efforts = effortChoices(await pinnedModelPromise);
|
|
170
|
+
const chosen = await pick(io, {
|
|
171
|
+
title: "How much reasoning?",
|
|
172
|
+
options: efforts.map((entry) => ({ label: entry.label, ...(entry.hint ? { hint: entry.hint } : {}) }))
|
|
173
|
+
}, banner);
|
|
174
|
+
if (chosen === undefined)
|
|
175
|
+
return cancel(io);
|
|
176
|
+
effort = efforts[chosen].effort;
|
|
177
|
+
}
|
|
164
178
|
const rows = (await contextPromise) ?? [];
|
|
165
179
|
if (rows.length > 0)
|
|
166
180
|
header = `${banner}${renderContextPanel(rows, { color: colorEnabled(), width: terminalWidth() })}\n\n`;
|
|
@@ -191,16 +205,34 @@ export async function runInteractiveConsult(io, deps) {
|
|
|
191
205
|
targetUrl = conversationThreadUrl(conversations[chosen].id);
|
|
192
206
|
}
|
|
193
207
|
else if (destination === "project") {
|
|
194
|
-
const
|
|
208
|
+
const detailed = (await deps.listProjectsWithIds?.()) ?? [];
|
|
209
|
+
const projects = detailed.length > 0 ? detailed : (await deps.listProjects()).map((name) => ({ id: "", name }));
|
|
195
210
|
if (projects.length === 0) {
|
|
196
211
|
io.write("\nNo projects are visible in the ChatGPT sidebar.\n");
|
|
197
212
|
return 1;
|
|
198
213
|
}
|
|
199
|
-
const chosen = await pick(io, { title: "Which project?", options: projects.map((
|
|
214
|
+
const chosen = await pick(io, { title: "Which project?", options: projects.map((entry) => ({ label: entry.name })) }, header);
|
|
200
215
|
if (chosen === undefined)
|
|
201
216
|
return cancel(io);
|
|
202
217
|
projectMode = "existing";
|
|
203
|
-
projectName = projects[chosen];
|
|
218
|
+
projectName = projects[chosen].name;
|
|
219
|
+
// Entering a project usually means going back to something in it, so
|
|
220
|
+
// offer its chats before starting yet another one.
|
|
221
|
+
const inside = conversationsInProject((await conversationsPromise) ?? [], projects[chosen].id || undefined);
|
|
222
|
+
if (inside.length > 0) {
|
|
223
|
+
const options = [
|
|
224
|
+
{ label: "Start a new chat in this project" },
|
|
225
|
+
...inside.map((entry) => ({ label: entry.title }))
|
|
226
|
+
];
|
|
227
|
+
const picked = await pick(io, { title: `${projects[chosen].name}`, options }, header);
|
|
228
|
+
if (picked === undefined)
|
|
229
|
+
return cancel(io);
|
|
230
|
+
if (picked > 0) {
|
|
231
|
+
targetUrl = conversationThreadUrl(inside[picked - 1].id);
|
|
232
|
+
projectMode = "current";
|
|
233
|
+
projectName = undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
204
236
|
}
|
|
205
237
|
else if (destination === "project-new") {
|
|
206
238
|
projectName = await askLine(io, `${CLEAR}${header}New project\n\n name: `);
|
|
@@ -225,6 +257,7 @@ export async function runInteractiveConsult(io, deps) {
|
|
|
225
257
|
projectMode,
|
|
226
258
|
...(projectName ? { projectName } : {}),
|
|
227
259
|
...(targetUrl ? { targetUrl } : {}),
|
|
260
|
+
...(effort ? { effort } : {}),
|
|
228
261
|
tools,
|
|
229
262
|
newChat: !targetUrl
|
|
230
263
|
};
|
package/dist/tui.js
CHANGED
|
@@ -28,6 +28,8 @@ export function consultArgsFromChoices(choices) {
|
|
|
28
28
|
// or a fresh chat alongside a pinned target, and rightly so.
|
|
29
29
|
if (choices.targetUrl) {
|
|
30
30
|
args.push("--target-url", choices.targetUrl, "--confirm-target");
|
|
31
|
+
if (choices.effort)
|
|
32
|
+
args.push("--effort", choices.effort);
|
|
31
33
|
for (const attachment of choices.attachments ?? [])
|
|
32
34
|
args.push("--attach", attachment);
|
|
33
35
|
for (const tool of choices.tools)
|
|
@@ -37,6 +39,8 @@ export function consultArgsFromChoices(choices) {
|
|
|
37
39
|
}
|
|
38
40
|
if (choices.newChat)
|
|
39
41
|
args.push("--new-chat");
|
|
42
|
+
if (choices.effort)
|
|
43
|
+
args.push("--effort", choices.effort);
|
|
40
44
|
if (choices.projectMode === "existing" || choices.projectMode === "new") {
|
|
41
45
|
const name = choices.projectName?.trim();
|
|
42
46
|
if (!name)
|
|
@@ -53,6 +57,46 @@ export function consultArgsFromChoices(choices) {
|
|
|
53
57
|
args.push("--", prompt);
|
|
54
58
|
return args;
|
|
55
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Projects with the id their conversations are tagged with.
|
|
62
|
+
*
|
|
63
|
+
* The sidebar gives names only, which is enough to ENTER a project but not to
|
|
64
|
+
* tell which chats live in it - and "open the project, then pick the session
|
|
65
|
+
* inside it" needs exactly that link.
|
|
66
|
+
*/
|
|
67
|
+
export function projectsWithIdsExpression() {
|
|
68
|
+
return `(async () => {
|
|
69
|
+
let token = "";
|
|
70
|
+
try {
|
|
71
|
+
const session = await fetch("/api/auth/session", { credentials: "include" });
|
|
72
|
+
if (!session.ok) return [];
|
|
73
|
+
const parsed = await session.json();
|
|
74
|
+
token = (parsed && parsed.accessToken) || "";
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch("/backend-api/gizmos/snorlax/sidebar", {
|
|
80
|
+
credentials: "include",
|
|
81
|
+
headers: token ? { Authorization: "Bearer " + token } : {}
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) return [];
|
|
84
|
+
const listed = await response.json();
|
|
85
|
+
return ((listed && listed.items) || [])
|
|
86
|
+
.map((item) => item && item.gizmo)
|
|
87
|
+
.filter((gizmo) => gizmo && gizmo.id)
|
|
88
|
+
.map((gizmo) => ({ id: gizmo.id, name: ((gizmo.display && gizmo.display.name) || gizmo.name || "").trim() || gizmo.id }));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
})()`;
|
|
93
|
+
}
|
|
94
|
+
/** The conversations that live inside a project, or all of them without one. */
|
|
95
|
+
export function conversationsInProject(conversations, projectId) {
|
|
96
|
+
if (!projectId)
|
|
97
|
+
return conversations;
|
|
98
|
+
return conversations.filter((conversation) => conversation.gizmoId === projectId);
|
|
99
|
+
}
|
|
56
100
|
/**
|
|
57
101
|
* Recent conversations with their titles, for the "continue an existing chat"
|
|
58
102
|
* list. Only the sidebar listing is fetched - the transcripts themselves are
|
|
@@ -78,7 +122,11 @@ export function recentConversationTitlesExpression(limit = 10) {
|
|
|
78
122
|
const listed = await response.json();
|
|
79
123
|
return ((listed && listed.items) || [])
|
|
80
124
|
.filter((item) => item && item.id)
|
|
81
|
-
.map((item) => ({
|
|
125
|
+
.map((item) => ({
|
|
126
|
+
id: item.id,
|
|
127
|
+
title: (item.title || "").trim() || "Untitled",
|
|
128
|
+
...(item.gizmo_id ? { gizmoId: item.gizmo_id } : {})
|
|
129
|
+
}));
|
|
82
130
|
} catch (error) {
|
|
83
131
|
return [];
|
|
84
132
|
}
|