vidpipe 1.3.16 → 1.3.17
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/cli.js +840 -66
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +136 -1
- package/dist/index.js +437 -19
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/cli.js
CHANGED
|
@@ -7904,10 +7904,14 @@ var init_CopilotProvider = __esm({
|
|
|
7904
7904
|
let response;
|
|
7905
7905
|
let sdkError;
|
|
7906
7906
|
try {
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7907
|
+
if (this.timeoutMs === 0) {
|
|
7908
|
+
response = await this.sendAndWaitForIdle(message);
|
|
7909
|
+
} else {
|
|
7910
|
+
response = await this.session.sendAndWait(
|
|
7911
|
+
{ prompt: message },
|
|
7912
|
+
this.timeoutMs
|
|
7913
|
+
);
|
|
7914
|
+
}
|
|
7911
7915
|
} catch (err) {
|
|
7912
7916
|
sdkError = err instanceof Error ? err : new Error(String(err));
|
|
7913
7917
|
if (sdkError.message.includes("missing finish_reason")) {
|
|
@@ -7931,6 +7935,38 @@ var init_CopilotProvider = __esm({
|
|
|
7931
7935
|
durationMs: Date.now() - start
|
|
7932
7936
|
};
|
|
7933
7937
|
}
|
|
7938
|
+
/**
|
|
7939
|
+
* Send a message and wait for session.idle without any timeout.
|
|
7940
|
+
* Used by interactive agents (interview, chat) where tool handlers
|
|
7941
|
+
* block waiting for human input — the SDK's sendAndWait() timeout
|
|
7942
|
+
* would fire while the agent is legitimately waiting for the user.
|
|
7943
|
+
*/
|
|
7944
|
+
sendAndWaitForIdle(message) {
|
|
7945
|
+
return new Promise((resolve3, reject) => {
|
|
7946
|
+
let lastAssistantMessage;
|
|
7947
|
+
const unsubMessage = this.session.on("assistant.message", (event) => {
|
|
7948
|
+
lastAssistantMessage = event;
|
|
7949
|
+
});
|
|
7950
|
+
const unsubIdle = this.session.on("session.idle", () => {
|
|
7951
|
+
unsubMessage();
|
|
7952
|
+
unsubIdle();
|
|
7953
|
+
unsubError();
|
|
7954
|
+
resolve3(lastAssistantMessage);
|
|
7955
|
+
});
|
|
7956
|
+
const unsubError = this.session.on("session.error", (event) => {
|
|
7957
|
+
unsubMessage();
|
|
7958
|
+
unsubIdle();
|
|
7959
|
+
unsubError();
|
|
7960
|
+
reject(new Error(event.data?.message ?? "Unknown session error"));
|
|
7961
|
+
});
|
|
7962
|
+
this.session.send({ prompt: message }).catch((err) => {
|
|
7963
|
+
unsubMessage();
|
|
7964
|
+
unsubIdle();
|
|
7965
|
+
unsubError();
|
|
7966
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
7967
|
+
});
|
|
7968
|
+
});
|
|
7969
|
+
}
|
|
7934
7970
|
on(event, handler) {
|
|
7935
7971
|
const handlers = this.eventHandlers.get(event) ?? [];
|
|
7936
7972
|
handlers.push(handler);
|
|
@@ -18812,6 +18848,352 @@ async function generateIdeas(options = {}) {
|
|
|
18812
18848
|
}
|
|
18813
18849
|
}
|
|
18814
18850
|
|
|
18851
|
+
// src/L4-agents/InterviewAgent.ts
|
|
18852
|
+
init_BaseAgent();
|
|
18853
|
+
|
|
18854
|
+
// src/L1-infra/progress/interviewEmitter.ts
|
|
18855
|
+
var InterviewEmitter = class {
|
|
18856
|
+
enabled = false;
|
|
18857
|
+
listeners = /* @__PURE__ */ new Set();
|
|
18858
|
+
/** Turn on interview event output to stderr. */
|
|
18859
|
+
enable() {
|
|
18860
|
+
this.enabled = true;
|
|
18861
|
+
}
|
|
18862
|
+
/** Turn off interview event output. */
|
|
18863
|
+
disable() {
|
|
18864
|
+
this.enabled = false;
|
|
18865
|
+
}
|
|
18866
|
+
/** Whether the emitter is currently active (stderr or listeners). */
|
|
18867
|
+
isEnabled() {
|
|
18868
|
+
return this.enabled || this.listeners.size > 0;
|
|
18869
|
+
}
|
|
18870
|
+
/** Register a programmatic listener for interview events. */
|
|
18871
|
+
addListener(fn) {
|
|
18872
|
+
this.listeners.add(fn);
|
|
18873
|
+
}
|
|
18874
|
+
/** Remove a previously registered listener. */
|
|
18875
|
+
removeListener(fn) {
|
|
18876
|
+
this.listeners.delete(fn);
|
|
18877
|
+
}
|
|
18878
|
+
/**
|
|
18879
|
+
* Write an interview event as a single JSON line to stderr (if enabled)
|
|
18880
|
+
* and dispatch to all registered listeners.
|
|
18881
|
+
* No-op when neither stderr output nor listeners are active.
|
|
18882
|
+
*/
|
|
18883
|
+
emit(event) {
|
|
18884
|
+
if (!this.enabled && this.listeners.size === 0) return;
|
|
18885
|
+
if (this.enabled) {
|
|
18886
|
+
process.stderr.write(JSON.stringify(event) + "\n");
|
|
18887
|
+
}
|
|
18888
|
+
for (const listener of this.listeners) {
|
|
18889
|
+
listener(event);
|
|
18890
|
+
}
|
|
18891
|
+
}
|
|
18892
|
+
};
|
|
18893
|
+
var interviewEmitter = new InterviewEmitter();
|
|
18894
|
+
|
|
18895
|
+
// src/L4-agents/InterviewAgent.ts
|
|
18896
|
+
init_configLogger();
|
|
18897
|
+
var SYSTEM_PROMPT8 = `You are a Socratic interview coach helping a content creator sharpen their video idea. Ask ONE short question at a time (1 sentence max).
|
|
18898
|
+
|
|
18899
|
+
## Rules
|
|
18900
|
+
- Every question must be a SINGLE sentence. No multi-part questions. No preamble. No encouragement filler.
|
|
18901
|
+
- Build on the previous answer \u2014 reference what the user said.
|
|
18902
|
+
- Push on weak spots: vague audience, generic hooks, surface-level talking points.
|
|
18903
|
+
- If the user responds with "/end", call end_interview immediately.
|
|
18904
|
+
|
|
18905
|
+
## Focus (pick one per question)
|
|
18906
|
+
- Problem clarity \u2014 what specific pain does this solve?
|
|
18907
|
+
- Audience \u2014 who exactly, what skill level?
|
|
18908
|
+
- Key takeaway \u2014 what's the ONE thing to remember?
|
|
18909
|
+
- Hook \u2014 would you click this? Be specific.
|
|
18910
|
+
- Talking points \u2014 substantive or surface-level?
|
|
18911
|
+
- Trend relevance \u2014 why now?
|
|
18912
|
+
|
|
18913
|
+
## Tools
|
|
18914
|
+
- ask_question: EVERY question goes through this tool. Include a 1-sentence rationale and the target field.
|
|
18915
|
+
- update_field: When the conversation reveals a better value for a field, DIRECTLY SET the new value. For scalar fields (hook, audience, keyTakeaway, trendContext), provide the complete replacement text. For array fields (talkingPoints), provide the FULL updated list \u2014 not just the new item. Write the actual content, not a description of the change.
|
|
18916
|
+
- end_interview: After 5\u201310 productive questions, wrap up with a brief summary.
|
|
18917
|
+
- NEVER output text outside of tool calls.`;
|
|
18918
|
+
var InterviewAgent = class extends BaseAgent {
|
|
18919
|
+
answerProvider = null;
|
|
18920
|
+
transcript = [];
|
|
18921
|
+
insights = {};
|
|
18922
|
+
questionNumber = 0;
|
|
18923
|
+
ended = false;
|
|
18924
|
+
idea = null;
|
|
18925
|
+
constructor(model) {
|
|
18926
|
+
super("InterviewAgent", SYSTEM_PROMPT8, void 0, model);
|
|
18927
|
+
}
|
|
18928
|
+
getTimeoutMs() {
|
|
18929
|
+
return 0;
|
|
18930
|
+
}
|
|
18931
|
+
resetForRetry() {
|
|
18932
|
+
this.transcript = [];
|
|
18933
|
+
this.insights = {};
|
|
18934
|
+
this.questionNumber = 0;
|
|
18935
|
+
this.ended = false;
|
|
18936
|
+
}
|
|
18937
|
+
getTools() {
|
|
18938
|
+
return [
|
|
18939
|
+
{
|
|
18940
|
+
name: "ask_question",
|
|
18941
|
+
description: "Ask the user a single Socratic question to explore and develop the idea. This is the primary way you communicate \u2014 every question MUST go through this tool.",
|
|
18942
|
+
parameters: {
|
|
18943
|
+
type: "object",
|
|
18944
|
+
properties: {
|
|
18945
|
+
question: {
|
|
18946
|
+
type: "string",
|
|
18947
|
+
description: "The question to ask the user. Must be a single, focused question."
|
|
18948
|
+
},
|
|
18949
|
+
rationale: {
|
|
18950
|
+
type: "string",
|
|
18951
|
+
description: "Why you are asking this question \u2014 what gap or opportunity it explores."
|
|
18952
|
+
},
|
|
18953
|
+
targetField: {
|
|
18954
|
+
type: "string",
|
|
18955
|
+
description: "Which idea field this question explores (e.g. hook, audience, keyTakeaway, talkingPoints, trendContext).",
|
|
18956
|
+
enum: ["topic", "hook", "audience", "keyTakeaway", "talkingPoints", "platforms", "tags", "publishBy", "trendContext"]
|
|
18957
|
+
}
|
|
18958
|
+
},
|
|
18959
|
+
required: ["question", "rationale"],
|
|
18960
|
+
additionalProperties: false
|
|
18961
|
+
},
|
|
18962
|
+
handler: async (args) => this.handleToolCall("ask_question", args)
|
|
18963
|
+
},
|
|
18964
|
+
{
|
|
18965
|
+
name: "update_field",
|
|
18966
|
+
description: "Directly update an idea field with new content discovered during the interview. For scalar fields, provide the complete replacement text. For talkingPoints, provide the FULL updated list (all points, not just new ones).",
|
|
18967
|
+
parameters: {
|
|
18968
|
+
type: "object",
|
|
18969
|
+
properties: {
|
|
18970
|
+
field: {
|
|
18971
|
+
type: "string",
|
|
18972
|
+
description: "Which idea field to update.",
|
|
18973
|
+
enum: ["topic", "hook", "audience", "keyTakeaway", "talkingPoints", "tags", "trendContext"]
|
|
18974
|
+
},
|
|
18975
|
+
value: {
|
|
18976
|
+
type: "string",
|
|
18977
|
+
description: "The new value for scalar fields (hook, audience, keyTakeaway, trendContext, topic)."
|
|
18978
|
+
},
|
|
18979
|
+
values: {
|
|
18980
|
+
type: "array",
|
|
18981
|
+
items: { type: "string" },
|
|
18982
|
+
description: "The full updated list for array fields (talkingPoints, tags). Include ALL items, not just new ones."
|
|
18983
|
+
}
|
|
18984
|
+
},
|
|
18985
|
+
required: ["field"],
|
|
18986
|
+
additionalProperties: false
|
|
18987
|
+
},
|
|
18988
|
+
handler: async (args) => this.handleToolCall("update_field", args)
|
|
18989
|
+
},
|
|
18990
|
+
{
|
|
18991
|
+
name: "end_interview",
|
|
18992
|
+
description: "Signal that the interview is complete. Use when you have gathered sufficient insights (typically after 5\u201310 questions) to meaningfully improve the idea.",
|
|
18993
|
+
parameters: {
|
|
18994
|
+
type: "object",
|
|
18995
|
+
properties: {
|
|
18996
|
+
summary: {
|
|
18997
|
+
type: "string",
|
|
18998
|
+
description: "A summary of what was learned and how the idea has been refined."
|
|
18999
|
+
}
|
|
19000
|
+
},
|
|
19001
|
+
required: ["summary"],
|
|
19002
|
+
additionalProperties: false
|
|
19003
|
+
},
|
|
19004
|
+
handler: async (args) => this.handleToolCall("end_interview", args)
|
|
19005
|
+
}
|
|
19006
|
+
];
|
|
19007
|
+
}
|
|
19008
|
+
async handleToolCall(toolName, args) {
|
|
19009
|
+
switch (toolName) {
|
|
19010
|
+
case "ask_question":
|
|
19011
|
+
return this.handleAskQuestion(args);
|
|
19012
|
+
case "update_field":
|
|
19013
|
+
return this.handleUpdateField(args);
|
|
19014
|
+
case "end_interview":
|
|
19015
|
+
return this.handleEndInterview(args);
|
|
19016
|
+
default:
|
|
19017
|
+
return { error: `Unknown tool: ${toolName}` };
|
|
19018
|
+
}
|
|
19019
|
+
}
|
|
19020
|
+
async handleAskQuestion(args) {
|
|
19021
|
+
const question = String(args.question ?? "");
|
|
19022
|
+
const rationale = String(args.rationale ?? "");
|
|
19023
|
+
const targetField = args.targetField;
|
|
19024
|
+
this.questionNumber++;
|
|
19025
|
+
const context = {
|
|
19026
|
+
rationale,
|
|
19027
|
+
targetField,
|
|
19028
|
+
questionNumber: this.questionNumber
|
|
19029
|
+
};
|
|
19030
|
+
interviewEmitter.emit({
|
|
19031
|
+
event: "question:asked",
|
|
19032
|
+
question,
|
|
19033
|
+
context,
|
|
19034
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19035
|
+
});
|
|
19036
|
+
logger_default.info(`[InterviewAgent] Q${this.questionNumber}: ${question}`);
|
|
19037
|
+
return this.waitForAnswer(question, context);
|
|
19038
|
+
}
|
|
19039
|
+
handleUpdateField(args) {
|
|
19040
|
+
const field = String(args.field ?? "");
|
|
19041
|
+
if (field === "talkingPoints" || field === "tags") {
|
|
19042
|
+
const values = args.values;
|
|
19043
|
+
if (values && values.length > 0) {
|
|
19044
|
+
this.insights[field] = values;
|
|
19045
|
+
}
|
|
19046
|
+
} else {
|
|
19047
|
+
const value = String(args.value ?? "");
|
|
19048
|
+
if (value) {
|
|
19049
|
+
this.insights[field] = value;
|
|
19050
|
+
}
|
|
19051
|
+
}
|
|
19052
|
+
const displayValue = field === "talkingPoints" || field === "tags" ? `[${(args.values ?? []).length} items]` : String(args.value ?? "").slice(0, 60);
|
|
19053
|
+
interviewEmitter.emit({
|
|
19054
|
+
event: "insight:discovered",
|
|
19055
|
+
insight: displayValue,
|
|
19056
|
+
field,
|
|
19057
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19058
|
+
});
|
|
19059
|
+
logger_default.info(`[InterviewAgent] Updated [${field}]: ${displayValue}`);
|
|
19060
|
+
return { updated: true, field };
|
|
19061
|
+
}
|
|
19062
|
+
handleEndInterview(args) {
|
|
19063
|
+
const summary = String(args.summary ?? "");
|
|
19064
|
+
this.ended = true;
|
|
19065
|
+
logger_default.info(`[InterviewAgent] Interview ended: ${summary}`);
|
|
19066
|
+
return { ended: true, summary };
|
|
19067
|
+
}
|
|
19068
|
+
async waitForAnswer(question, context) {
|
|
19069
|
+
if (!this.answerProvider) {
|
|
19070
|
+
throw new Error("No answer provider configured \u2014 cannot ask questions");
|
|
19071
|
+
}
|
|
19072
|
+
const askedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19073
|
+
const answer = await this.answerProvider(question, context);
|
|
19074
|
+
const answeredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19075
|
+
const pair = {
|
|
19076
|
+
question,
|
|
19077
|
+
answer,
|
|
19078
|
+
askedAt,
|
|
19079
|
+
answeredAt,
|
|
19080
|
+
questionNumber: context.questionNumber
|
|
19081
|
+
};
|
|
19082
|
+
this.transcript.push(pair);
|
|
19083
|
+
interviewEmitter.emit({
|
|
19084
|
+
event: "answer:received",
|
|
19085
|
+
questionNumber: context.questionNumber,
|
|
19086
|
+
answer,
|
|
19087
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19088
|
+
});
|
|
19089
|
+
return answer;
|
|
19090
|
+
}
|
|
19091
|
+
/**
|
|
19092
|
+
* Run a Socratic interview session for the given idea.
|
|
19093
|
+
*
|
|
19094
|
+
* The agent uses `ask_question` tool calls to present questions one at a time.
|
|
19095
|
+
* Each question is routed through the `answerProvider` callback, which the caller
|
|
19096
|
+
* implements to show the question to the user and collect their response.
|
|
19097
|
+
*/
|
|
19098
|
+
async runInterview(idea, answerProvider) {
|
|
19099
|
+
this.idea = idea;
|
|
19100
|
+
this.answerProvider = answerProvider;
|
|
19101
|
+
this.transcript = [];
|
|
19102
|
+
this.insights = {};
|
|
19103
|
+
this.questionNumber = 0;
|
|
19104
|
+
this.ended = false;
|
|
19105
|
+
const startTime = Date.now();
|
|
19106
|
+
interviewEmitter.emit({
|
|
19107
|
+
event: "interview:start",
|
|
19108
|
+
ideaNumber: idea.issueNumber,
|
|
19109
|
+
mode: "interview",
|
|
19110
|
+
ideaTopic: idea.topic,
|
|
19111
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19112
|
+
});
|
|
19113
|
+
const contextMessage = this.buildIdeaContext(idea);
|
|
19114
|
+
try {
|
|
19115
|
+
await this.run(contextMessage);
|
|
19116
|
+
const result = {
|
|
19117
|
+
ideaNumber: idea.issueNumber,
|
|
19118
|
+
transcript: this.transcript,
|
|
19119
|
+
insights: this.insights,
|
|
19120
|
+
updatedFields: this.getUpdatedFields(),
|
|
19121
|
+
durationMs: Date.now() - startTime,
|
|
19122
|
+
endedBy: this.ended ? "agent" : "user"
|
|
19123
|
+
};
|
|
19124
|
+
interviewEmitter.emit({
|
|
19125
|
+
event: "interview:complete",
|
|
19126
|
+
result,
|
|
19127
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19128
|
+
});
|
|
19129
|
+
return result;
|
|
19130
|
+
} catch (error) {
|
|
19131
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19132
|
+
interviewEmitter.emit({
|
|
19133
|
+
event: "interview:error",
|
|
19134
|
+
error: message,
|
|
19135
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19136
|
+
});
|
|
19137
|
+
throw error;
|
|
19138
|
+
}
|
|
19139
|
+
}
|
|
19140
|
+
buildIdeaContext(idea) {
|
|
19141
|
+
const talkingPoints = idea.talkingPoints.length > 0 ? idea.talkingPoints.map((p) => `- ${p}`).join("\n") : "- (none yet)";
|
|
19142
|
+
return [
|
|
19143
|
+
"Here is the idea to explore through Socratic questioning:",
|
|
19144
|
+
"",
|
|
19145
|
+
`**Topic:** ${idea.topic}`,
|
|
19146
|
+
`**Hook:** ${idea.hook}`,
|
|
19147
|
+
`**Audience:** ${idea.audience}`,
|
|
19148
|
+
`**Key Takeaway:** ${idea.keyTakeaway}`,
|
|
19149
|
+
"**Talking Points:**",
|
|
19150
|
+
talkingPoints,
|
|
19151
|
+
`**Publish By:** ${idea.publishBy}`,
|
|
19152
|
+
`**Trend Context:** ${idea.trendContext ?? "Not specified"}`,
|
|
19153
|
+
"",
|
|
19154
|
+
"Begin by asking your first Socratic question to explore and develop this idea."
|
|
19155
|
+
].join("\n");
|
|
19156
|
+
}
|
|
19157
|
+
getUpdatedFields() {
|
|
19158
|
+
const fields = [];
|
|
19159
|
+
if (this.insights.talkingPoints !== void 0) fields.push("talkingPoints");
|
|
19160
|
+
if (this.insights.keyTakeaway !== void 0) fields.push("keyTakeaway");
|
|
19161
|
+
if (this.insights.hook !== void 0) fields.push("hook");
|
|
19162
|
+
if (this.insights.audience !== void 0) fields.push("audience");
|
|
19163
|
+
if (this.insights.trendContext !== void 0) fields.push("trendContext");
|
|
19164
|
+
if (this.insights.tags !== void 0) fields.push("tags");
|
|
19165
|
+
return fields;
|
|
19166
|
+
}
|
|
19167
|
+
setupEventHandlers(session) {
|
|
19168
|
+
session.on("delta", () => {
|
|
19169
|
+
});
|
|
19170
|
+
session.on("tool_start", (event) => {
|
|
19171
|
+
const toolName = event.data?.name ?? "unknown";
|
|
19172
|
+
if (toolName !== "ask_question") {
|
|
19173
|
+
interviewEmitter.emit({
|
|
19174
|
+
event: "tool:start",
|
|
19175
|
+
toolName,
|
|
19176
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19177
|
+
});
|
|
19178
|
+
}
|
|
19179
|
+
});
|
|
19180
|
+
session.on("tool_end", (event) => {
|
|
19181
|
+
const toolName = event.data?.name ?? "unknown";
|
|
19182
|
+
if (toolName !== "ask_question") {
|
|
19183
|
+
interviewEmitter.emit({
|
|
19184
|
+
event: "tool:end",
|
|
19185
|
+
toolName,
|
|
19186
|
+
durationMs: 0,
|
|
19187
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19188
|
+
});
|
|
19189
|
+
}
|
|
19190
|
+
});
|
|
19191
|
+
session.on("error", (event) => {
|
|
19192
|
+
logger_default.error(`[InterviewAgent] error: ${JSON.stringify(event.data)}`);
|
|
19193
|
+
});
|
|
19194
|
+
}
|
|
19195
|
+
};
|
|
19196
|
+
|
|
18815
19197
|
// src/L5-assets/pipelineServices.ts
|
|
18816
19198
|
var costTracker3 = {
|
|
18817
19199
|
reset: (...args) => costTracker2.reset(...args),
|
|
@@ -18835,6 +19217,9 @@ function markFailed3(...args) {
|
|
|
18835
19217
|
function generateIdeas2(...args) {
|
|
18836
19218
|
return generateIdeas(...args);
|
|
18837
19219
|
}
|
|
19220
|
+
function createInterviewAgent(...args) {
|
|
19221
|
+
return new InterviewAgent(...args);
|
|
19222
|
+
}
|
|
18838
19223
|
function createScheduleAgent(...args) {
|
|
18839
19224
|
return new ScheduleAgent(...args);
|
|
18840
19225
|
}
|
|
@@ -19789,15 +20174,225 @@ async function runRealign(options = {}) {
|
|
|
19789
20174
|
init_environment();
|
|
19790
20175
|
init_configLogger();
|
|
19791
20176
|
|
|
19792
|
-
// src/L1-infra/
|
|
19793
|
-
import {
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
|
|
19798
|
-
|
|
20177
|
+
// src/L1-infra/terminal/altScreenChat.tsx
|
|
20178
|
+
import { useState, useCallback, useEffect } from "react";
|
|
20179
|
+
import { render, Box, Text, useInput, useApp, useStdout } from "ink";
|
|
20180
|
+
import TextInput from "ink-text-input";
|
|
20181
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
20182
|
+
function Header({ title, subtitle }) {
|
|
20183
|
+
const { stdout } = useStdout();
|
|
20184
|
+
const cols = stdout?.columns ?? 80;
|
|
20185
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: cols, children: [
|
|
20186
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { backgroundColor: "blue", color: "white", bold: true, children: ` \u{1F4DD} ${title}`.padEnd(cols) }) }),
|
|
20187
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { backgroundColor: "blue", color: "white", dimColor: true, children: ` ${subtitle}`.padEnd(cols) }) }),
|
|
20188
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(cols) })
|
|
20189
|
+
] });
|
|
20190
|
+
}
|
|
20191
|
+
var FIELD_EMOJI = {
|
|
20192
|
+
topic: "\u{1F4CC} topic",
|
|
20193
|
+
hook: "\u{1FA9D} hook",
|
|
20194
|
+
audience: "\u{1F3AF} audience",
|
|
20195
|
+
keyTakeaway: "\u{1F48E} takeaway",
|
|
20196
|
+
talkingPoints: "\u{1F4CB} talking points",
|
|
20197
|
+
platforms: "\u{1F4F1} platforms",
|
|
20198
|
+
tags: "\u{1F3F7}\uFE0F tags",
|
|
20199
|
+
publishBy: "\u{1F4C5} deadline",
|
|
20200
|
+
trendContext: "\u{1F525} trend"
|
|
20201
|
+
};
|
|
20202
|
+
function QuestionCardView({ card, latestInsight, statusText }) {
|
|
20203
|
+
if (!card) {
|
|
20204
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: statusText || "Preparing first question..." }) });
|
|
20205
|
+
}
|
|
20206
|
+
const fieldLabel = FIELD_EMOJI[card.targetField] ?? `\u{1F4CE} ${card.targetField}`;
|
|
20207
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingLeft: 2, paddingRight: 2, children: [
|
|
20208
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
20209
|
+
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", children: [
|
|
20210
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
20211
|
+
"Question ",
|
|
20212
|
+
card.questionNumber
|
|
20213
|
+
] }),
|
|
20214
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: fieldLabel })
|
|
20215
|
+
] }),
|
|
20216
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
20217
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, wrap: "wrap", children: card.question }),
|
|
20218
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
20219
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
20220
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u{1F4AD} " }),
|
|
20221
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: card.rationale })
|
|
20222
|
+
] }),
|
|
20223
|
+
latestInsight && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
20224
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
20225
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
20226
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u{1F4A1} " }),
|
|
20227
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", wrap: "wrap", children: latestInsight })
|
|
20228
|
+
] })
|
|
20229
|
+
] }),
|
|
20230
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
20231
|
+
statusText && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: statusText }) })
|
|
20232
|
+
] });
|
|
20233
|
+
}
|
|
20234
|
+
function InputLine({ prompt, value, onChange, onSubmit, active }) {
|
|
20235
|
+
const { stdout } = useStdout();
|
|
20236
|
+
const cols = stdout?.columns ?? 80;
|
|
20237
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
20238
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(cols) }),
|
|
20239
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
20240
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: prompt }),
|
|
20241
|
+
active ? /* @__PURE__ */ jsx(TextInput, { value, onChange, onSubmit }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: " waiting..." })
|
|
20242
|
+
] })
|
|
20243
|
+
] });
|
|
20244
|
+
}
|
|
20245
|
+
function ChatApp({ controller }) {
|
|
20246
|
+
const { exit } = useApp();
|
|
20247
|
+
const [card, setCard] = useState(null);
|
|
20248
|
+
const [latestInsight, setLatestInsight] = useState(null);
|
|
20249
|
+
const [statusText, setStatusText] = useState("");
|
|
20250
|
+
const [inputValue, setInputValue] = useState("");
|
|
20251
|
+
const [inputActive, setInputActive] = useState(false);
|
|
20252
|
+
const [, setTick] = useState(0);
|
|
20253
|
+
useEffect(() => {
|
|
20254
|
+
controller._wire({
|
|
20255
|
+
setCard,
|
|
20256
|
+
setLatestInsight,
|
|
20257
|
+
setStatusText,
|
|
20258
|
+
setInputActive,
|
|
20259
|
+
exit,
|
|
20260
|
+
forceRender: () => setTick((t) => t + 1)
|
|
20261
|
+
});
|
|
20262
|
+
return () => controller._unwire();
|
|
20263
|
+
}, [controller, exit]);
|
|
20264
|
+
useInput((_input, key) => {
|
|
20265
|
+
if (key.ctrl && _input === "c") {
|
|
20266
|
+
controller.interrupted = true;
|
|
20267
|
+
const resolve3 = controller._pendingResolve;
|
|
20268
|
+
controller._pendingResolve = null;
|
|
20269
|
+
if (resolve3) resolve3("");
|
|
20270
|
+
exit();
|
|
20271
|
+
}
|
|
19799
20272
|
});
|
|
19800
|
-
|
|
20273
|
+
const handleSubmit = useCallback((value) => {
|
|
20274
|
+
setInputValue("");
|
|
20275
|
+
setInputActive(false);
|
|
20276
|
+
const resolve3 = controller._pendingResolve;
|
|
20277
|
+
controller._pendingResolve = null;
|
|
20278
|
+
if (resolve3) resolve3(value.trim());
|
|
20279
|
+
}, [controller]);
|
|
20280
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: "100%", children: [
|
|
20281
|
+
/* @__PURE__ */ jsx(
|
|
20282
|
+
Header,
|
|
20283
|
+
{
|
|
20284
|
+
title: controller.title,
|
|
20285
|
+
subtitle: controller.subtitle
|
|
20286
|
+
}
|
|
20287
|
+
),
|
|
20288
|
+
/* @__PURE__ */ jsx(
|
|
20289
|
+
QuestionCardView,
|
|
20290
|
+
{
|
|
20291
|
+
card,
|
|
20292
|
+
latestInsight,
|
|
20293
|
+
statusText
|
|
20294
|
+
}
|
|
20295
|
+
),
|
|
20296
|
+
/* @__PURE__ */ jsx(
|
|
20297
|
+
InputLine,
|
|
20298
|
+
{
|
|
20299
|
+
prompt: controller.inputPrompt,
|
|
20300
|
+
value: inputValue,
|
|
20301
|
+
onChange: setInputValue,
|
|
20302
|
+
onSubmit: handleSubmit,
|
|
20303
|
+
active: inputActive
|
|
20304
|
+
}
|
|
20305
|
+
)
|
|
20306
|
+
] });
|
|
20307
|
+
}
|
|
20308
|
+
var AltScreenChat = class {
|
|
20309
|
+
title;
|
|
20310
|
+
subtitle;
|
|
20311
|
+
inputPrompt;
|
|
20312
|
+
maxScrollback;
|
|
20313
|
+
messages = [];
|
|
20314
|
+
bridge = null;
|
|
20315
|
+
inkInstance = null;
|
|
20316
|
+
/** Set to true when Ctrl+C is pressed. Callers should check this after promptInput(). */
|
|
20317
|
+
interrupted = false;
|
|
20318
|
+
/** @internal */
|
|
20319
|
+
_pendingResolve = null;
|
|
20320
|
+
constructor(options) {
|
|
20321
|
+
this.title = options.title;
|
|
20322
|
+
this.subtitle = options.subtitle ?? "Type /end to finish, Ctrl+C to quit";
|
|
20323
|
+
this.inputPrompt = options.inputPrompt ?? "> ";
|
|
20324
|
+
this.maxScrollback = options.maxScrollback ?? 500;
|
|
20325
|
+
}
|
|
20326
|
+
/** @internal */
|
|
20327
|
+
_wire(bridge) {
|
|
20328
|
+
this.bridge = bridge;
|
|
20329
|
+
}
|
|
20330
|
+
/** @internal */
|
|
20331
|
+
_unwire() {
|
|
20332
|
+
this.bridge = null;
|
|
20333
|
+
}
|
|
20334
|
+
/** Enter fullscreen and render the Ink UI. */
|
|
20335
|
+
enter() {
|
|
20336
|
+
this.inkInstance = render(
|
|
20337
|
+
/* @__PURE__ */ jsx(ChatApp, { controller: this }),
|
|
20338
|
+
{ exitOnCtrlC: false }
|
|
20339
|
+
);
|
|
20340
|
+
}
|
|
20341
|
+
/** Leave fullscreen and clean up Ink. */
|
|
20342
|
+
leave() {
|
|
20343
|
+
if (this.inkInstance) {
|
|
20344
|
+
this.inkInstance.unmount();
|
|
20345
|
+
this.inkInstance = null;
|
|
20346
|
+
}
|
|
20347
|
+
}
|
|
20348
|
+
/** Clean up everything. */
|
|
20349
|
+
destroy() {
|
|
20350
|
+
this.leave();
|
|
20351
|
+
this.messages = [];
|
|
20352
|
+
this.bridge = null;
|
|
20353
|
+
this._pendingResolve = null;
|
|
20354
|
+
}
|
|
20355
|
+
/**
|
|
20356
|
+
* Show a focused question card. Replaces the entire display content
|
|
20357
|
+
* with this one question — no scrolling chat history.
|
|
20358
|
+
*/
|
|
20359
|
+
showQuestion(question, rationale, targetField, questionNumber) {
|
|
20360
|
+
this.bridge?.setCard({ question, rationale, targetField, questionNumber });
|
|
20361
|
+
}
|
|
20362
|
+
/**
|
|
20363
|
+
* Show a discovered insight on the current card.
|
|
20364
|
+
*/
|
|
20365
|
+
showInsight(text) {
|
|
20366
|
+
this.bridge?.setLatestInsight(text);
|
|
20367
|
+
}
|
|
20368
|
+
/**
|
|
20369
|
+
* Add a message to the internal log (for transcript purposes).
|
|
20370
|
+
* Does NOT affect the card display — use showQuestion for that.
|
|
20371
|
+
*/
|
|
20372
|
+
addMessage(role, content) {
|
|
20373
|
+
const msg = { role, content, timestamp: /* @__PURE__ */ new Date() };
|
|
20374
|
+
this.messages.push(msg);
|
|
20375
|
+
if (this.messages.length > this.maxScrollback) {
|
|
20376
|
+
this.messages = this.messages.slice(-this.maxScrollback);
|
|
20377
|
+
}
|
|
20378
|
+
}
|
|
20379
|
+
/** Set the status bar text. */
|
|
20380
|
+
setStatus(text) {
|
|
20381
|
+
this.bridge?.setStatusText(text);
|
|
20382
|
+
}
|
|
20383
|
+
/** Clear the status bar. */
|
|
20384
|
+
clearStatus() {
|
|
20385
|
+
this.bridge?.setStatusText("");
|
|
20386
|
+
}
|
|
20387
|
+
/** Prompt for user input. Returns their trimmed text. */
|
|
20388
|
+
promptInput(_prompt) {
|
|
20389
|
+
return new Promise((resolve3) => {
|
|
20390
|
+
this._pendingResolve = resolve3;
|
|
20391
|
+
this.bridge?.setInputActive(true);
|
|
20392
|
+
this.bridge?.forceRender();
|
|
20393
|
+
});
|
|
20394
|
+
}
|
|
20395
|
+
};
|
|
19801
20396
|
|
|
19802
20397
|
// src/L6-pipeline/scheduleChat.ts
|
|
19803
20398
|
function createScheduleAgent2(...args) {
|
|
@@ -19808,21 +20403,21 @@ function createScheduleAgent2(...args) {
|
|
|
19808
20403
|
async function runChat() {
|
|
19809
20404
|
initConfig();
|
|
19810
20405
|
setChatMode(true);
|
|
19811
|
-
const
|
|
20406
|
+
const chat = new AltScreenChat({
|
|
20407
|
+
title: "\u{1F4AC} VidPipe Chat",
|
|
20408
|
+
subtitle: "Schedule management assistant. Type exit or quit to leave.",
|
|
20409
|
+
inputPrompt: "vidpipe> "
|
|
20410
|
+
});
|
|
19812
20411
|
const handleUserInput = (request) => {
|
|
20412
|
+
chat.addMessage("agent", request.question);
|
|
20413
|
+
if (request.choices && request.choices.length > 0) {
|
|
20414
|
+
const choiceText = request.choices.map((c, i) => ` ${i + 1}. ${c}`).join("\n");
|
|
20415
|
+
chat.addMessage("system", choiceText + (request.allowFreeform !== false ? "\n (or type a custom answer)" : ""));
|
|
20416
|
+
}
|
|
19813
20417
|
return new Promise((resolve3) => {
|
|
19814
|
-
|
|
19815
|
-
console.log(`\x1B[33m\u{1F916} Agent asks:\x1B[0m ${request.question}`);
|
|
19816
|
-
if (request.choices && request.choices.length > 0) {
|
|
19817
|
-
for (let i = 0; i < request.choices.length; i++) {
|
|
19818
|
-
console.log(` ${i + 1}. ${request.choices[i]}`);
|
|
19819
|
-
}
|
|
19820
|
-
if (request.allowFreeform !== false) {
|
|
19821
|
-
console.log(` (or type a custom answer)`);
|
|
19822
|
-
}
|
|
19823
|
-
}
|
|
19824
|
-
rl2.question("\x1B[33m> \x1B[0m", (answer) => {
|
|
20418
|
+
chat.promptInput("> ").then((answer) => {
|
|
19825
20419
|
const trimmed = answer.trim();
|
|
20420
|
+
chat.addMessage("user", trimmed);
|
|
19826
20421
|
if (request.choices && request.choices.length > 0) {
|
|
19827
20422
|
const num = parseInt(trimmed, 10);
|
|
19828
20423
|
if (num >= 1 && num <= request.choices.length) {
|
|
@@ -19836,61 +20431,36 @@ async function runChat() {
|
|
|
19836
20431
|
};
|
|
19837
20432
|
const agent = createScheduleAgent2(handleUserInput);
|
|
19838
20433
|
agent.setChatOutput((message) => {
|
|
19839
|
-
|
|
19840
|
-
`);
|
|
19841
|
-
});
|
|
19842
|
-
console.log(`
|
|
19843
|
-
\x1B[36m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
19844
|
-
\u2551 VidPipe Chat \u2551
|
|
19845
|
-
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m
|
|
19846
|
-
|
|
19847
|
-
Schedule management assistant. Ask me about your posting schedule,
|
|
19848
|
-
reschedule posts, check what's coming up, or reprioritize content.
|
|
19849
|
-
|
|
19850
|
-
Type \x1B[33mexit\x1B[0m or \x1B[33mquit\x1B[0m to leave. Press Ctrl+C to stop.
|
|
19851
|
-
`);
|
|
19852
|
-
let closeRejector = null;
|
|
19853
|
-
const closePromise = new Promise((_, reject) => {
|
|
19854
|
-
closeRejector = reject;
|
|
19855
|
-
rl2.once("close", () => reject(new Error("readline closed")));
|
|
20434
|
+
chat.setStatus(message);
|
|
19856
20435
|
});
|
|
19857
|
-
|
|
19858
|
-
|
|
19859
|
-
new Promise((resolve3) => {
|
|
19860
|
-
rl2.question("\x1B[32mvidpipe>\x1B[0m ", (answer) => {
|
|
19861
|
-
resolve3(answer);
|
|
19862
|
-
});
|
|
19863
|
-
}),
|
|
19864
|
-
closePromise
|
|
19865
|
-
]);
|
|
19866
|
-
};
|
|
20436
|
+
chat.enter();
|
|
20437
|
+
chat.addMessage("system", "Ask me about your posting schedule, reschedule posts, check what's coming up, or reprioritize content.");
|
|
19867
20438
|
try {
|
|
19868
20439
|
while (true) {
|
|
19869
|
-
|
|
19870
|
-
try {
|
|
19871
|
-
input = await prompt();
|
|
19872
|
-
} catch {
|
|
19873
|
-
break;
|
|
19874
|
-
}
|
|
20440
|
+
const input = await chat.promptInput();
|
|
19875
20441
|
const trimmed = input.trim();
|
|
19876
20442
|
if (!trimmed) continue;
|
|
19877
20443
|
if (trimmed === "exit" || trimmed === "quit") {
|
|
19878
|
-
|
|
20444
|
+
chat.addMessage("system", "Goodbye! \u{1F44B}");
|
|
19879
20445
|
break;
|
|
19880
20446
|
}
|
|
20447
|
+
chat.addMessage("user", trimmed);
|
|
20448
|
+
chat.setStatus("\u{1F914} Thinking...");
|
|
19881
20449
|
try {
|
|
19882
|
-
await agent.run(trimmed);
|
|
19883
|
-
|
|
20450
|
+
const response = await agent.run(trimmed);
|
|
20451
|
+
chat.clearStatus();
|
|
20452
|
+
if (response) {
|
|
20453
|
+
chat.addMessage("agent", response);
|
|
20454
|
+
}
|
|
19884
20455
|
} catch (err) {
|
|
20456
|
+
chat.clearStatus();
|
|
19885
20457
|
const message = err instanceof Error ? err.message : String(err);
|
|
19886
|
-
|
|
19887
|
-
\x1B[31mError: ${message}\x1B[0m
|
|
19888
|
-
`);
|
|
20458
|
+
chat.addMessage("error", message);
|
|
19889
20459
|
}
|
|
19890
20460
|
}
|
|
19891
20461
|
} finally {
|
|
19892
20462
|
await agent.destroy();
|
|
19893
|
-
|
|
20463
|
+
chat.destroy();
|
|
19894
20464
|
setChatMode(false);
|
|
19895
20465
|
}
|
|
19896
20466
|
}
|
|
@@ -19903,6 +20473,16 @@ init_ideaService();
|
|
|
19903
20473
|
function generateIdeas3(...args) {
|
|
19904
20474
|
return generateIdeas2(...args);
|
|
19905
20475
|
}
|
|
20476
|
+
async function startInterview(idea, answerProvider, onEvent) {
|
|
20477
|
+
if (onEvent) interviewEmitter.addListener(onEvent);
|
|
20478
|
+
const agent = createInterviewAgent();
|
|
20479
|
+
try {
|
|
20480
|
+
return await agent.runInterview(idea, answerProvider);
|
|
20481
|
+
} finally {
|
|
20482
|
+
await agent.destroy();
|
|
20483
|
+
if (onEvent) interviewEmitter.removeListener(onEvent);
|
|
20484
|
+
}
|
|
20485
|
+
}
|
|
19906
20486
|
|
|
19907
20487
|
// src/L7-app/commands/ideate.ts
|
|
19908
20488
|
init_types();
|
|
@@ -20072,10 +20652,200 @@ async function handleAdd(options) {
|
|
|
20072
20652
|
}
|
|
20073
20653
|
}
|
|
20074
20654
|
|
|
20655
|
+
// src/L7-app/commands/ideateStart.ts
|
|
20656
|
+
init_environment();
|
|
20657
|
+
init_configLogger();
|
|
20658
|
+
|
|
20659
|
+
// src/L3-services/interview/interviewService.ts
|
|
20660
|
+
init_configLogger();
|
|
20661
|
+
init_githubClient();
|
|
20662
|
+
init_ideaService();
|
|
20663
|
+
async function loadAndValidateIdea(issueNumber) {
|
|
20664
|
+
const idea = await getIdea(issueNumber);
|
|
20665
|
+
if (!idea) {
|
|
20666
|
+
throw new Error(`Idea #${issueNumber} not found`);
|
|
20667
|
+
}
|
|
20668
|
+
if (idea.status !== "draft") {
|
|
20669
|
+
throw new Error(
|
|
20670
|
+
`Idea #${issueNumber} has status "${idea.status}" \u2014 only draft ideas can be started`
|
|
20671
|
+
);
|
|
20672
|
+
}
|
|
20673
|
+
return idea;
|
|
20674
|
+
}
|
|
20675
|
+
function formatTranscriptComment(transcript) {
|
|
20676
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20677
|
+
const date = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
|
|
20678
|
+
year: "numeric",
|
|
20679
|
+
month: "long",
|
|
20680
|
+
day: "numeric"
|
|
20681
|
+
});
|
|
20682
|
+
const lines = [
|
|
20683
|
+
"<!-- vidpipe:idea-comment -->",
|
|
20684
|
+
`<!-- {"type":"interview-transcript","savedAt":"${now}"} -->`,
|
|
20685
|
+
"",
|
|
20686
|
+
"## \u{1F399}\uFE0F Interview Transcript",
|
|
20687
|
+
"",
|
|
20688
|
+
`**Questions asked:** ${transcript.length}`,
|
|
20689
|
+
`**Date:** ${date}`,
|
|
20690
|
+
"",
|
|
20691
|
+
"---"
|
|
20692
|
+
];
|
|
20693
|
+
for (const qa of transcript) {
|
|
20694
|
+
lines.push("");
|
|
20695
|
+
lines.push(`### Q${qa.questionNumber}: ${qa.question}`);
|
|
20696
|
+
lines.push(`> ${qa.answer}`);
|
|
20697
|
+
}
|
|
20698
|
+
return lines.join("\n");
|
|
20699
|
+
}
|
|
20700
|
+
async function saveTranscript(issueNumber, transcript) {
|
|
20701
|
+
const body = formatTranscriptComment(transcript);
|
|
20702
|
+
const client = getGitHubClient();
|
|
20703
|
+
await client.addComment(issueNumber, body);
|
|
20704
|
+
logger_default.info(`Saved interview transcript (${transcript.length} Q&A) to issue #${issueNumber}`);
|
|
20705
|
+
}
|
|
20706
|
+
async function updateIdeaFromInsights(issueNumber, insights) {
|
|
20707
|
+
const updates = {};
|
|
20708
|
+
const updatedFields = [];
|
|
20709
|
+
if (insights.hook !== void 0) {
|
|
20710
|
+
updates.hook = insights.hook;
|
|
20711
|
+
updatedFields.push("hook");
|
|
20712
|
+
}
|
|
20713
|
+
if (insights.audience !== void 0) {
|
|
20714
|
+
updates.audience = insights.audience;
|
|
20715
|
+
updatedFields.push("audience");
|
|
20716
|
+
}
|
|
20717
|
+
if (insights.keyTakeaway !== void 0) {
|
|
20718
|
+
updates.keyTakeaway = insights.keyTakeaway;
|
|
20719
|
+
updatedFields.push("keyTakeaway");
|
|
20720
|
+
}
|
|
20721
|
+
if (insights.trendContext !== void 0) {
|
|
20722
|
+
updates.trendContext = insights.trendContext;
|
|
20723
|
+
updatedFields.push("trendContext");
|
|
20724
|
+
}
|
|
20725
|
+
if (insights.talkingPoints !== void 0 && insights.talkingPoints.length > 0) {
|
|
20726
|
+
updates.talkingPoints = insights.talkingPoints;
|
|
20727
|
+
updatedFields.push("talkingPoints");
|
|
20728
|
+
}
|
|
20729
|
+
if (insights.tags !== void 0 && insights.tags.length > 0) {
|
|
20730
|
+
updates.tags = insights.tags;
|
|
20731
|
+
updatedFields.push("tags");
|
|
20732
|
+
}
|
|
20733
|
+
if (updatedFields.length === 0) {
|
|
20734
|
+
logger_default.info(`No fields to update for idea #${issueNumber} from insights`);
|
|
20735
|
+
return;
|
|
20736
|
+
}
|
|
20737
|
+
await updateIdea(issueNumber, updates);
|
|
20738
|
+
logger_default.info(
|
|
20739
|
+
`Updated idea #${issueNumber} fields: ${updatedFields.join(", ")}`
|
|
20740
|
+
);
|
|
20741
|
+
}
|
|
20742
|
+
|
|
20743
|
+
// src/L7-app/commands/ideateStart.ts
|
|
20744
|
+
init_ideaService();
|
|
20745
|
+
var VALID_MODES = ["interview"];
|
|
20746
|
+
async function runIdeateStart(issueNumber, options) {
|
|
20747
|
+
const parsed = Number.parseInt(issueNumber, 10);
|
|
20748
|
+
if (Number.isNaN(parsed) || parsed < 1) {
|
|
20749
|
+
console.error(`Invalid issue number: "${issueNumber}". Must be a positive integer.`);
|
|
20750
|
+
process.exit(1);
|
|
20751
|
+
}
|
|
20752
|
+
initConfig();
|
|
20753
|
+
const mode = options.mode ?? "interview";
|
|
20754
|
+
if (!VALID_MODES.includes(mode)) {
|
|
20755
|
+
console.error(`Unknown mode: "${options.mode}". Valid modes: ${VALID_MODES.join(", ")}`);
|
|
20756
|
+
process.exit(1);
|
|
20757
|
+
}
|
|
20758
|
+
if (options.progress) {
|
|
20759
|
+
interviewEmitter.enable();
|
|
20760
|
+
}
|
|
20761
|
+
const idea = await loadAndValidateIdea(parsed);
|
|
20762
|
+
const chat = new AltScreenChat({
|
|
20763
|
+
title: `\u{1F4DD} Interview: ${idea.topic}`,
|
|
20764
|
+
subtitle: "Type /end to finish the interview. Press Ctrl+C to save and quit.",
|
|
20765
|
+
inputPrompt: "Your answer> "
|
|
20766
|
+
});
|
|
20767
|
+
const answerProvider = async (question, context) => {
|
|
20768
|
+
chat.showQuestion(
|
|
20769
|
+
question,
|
|
20770
|
+
context.rationale,
|
|
20771
|
+
context.targetField ? String(context.targetField) : "general",
|
|
20772
|
+
context.questionNumber
|
|
20773
|
+
);
|
|
20774
|
+
const answer = await chat.promptInput();
|
|
20775
|
+
if (chat.interrupted) {
|
|
20776
|
+
return "/end";
|
|
20777
|
+
}
|
|
20778
|
+
chat.addMessage("agent", question);
|
|
20779
|
+
chat.addMessage("user", answer);
|
|
20780
|
+
return answer;
|
|
20781
|
+
};
|
|
20782
|
+
const handleEvent = (event) => {
|
|
20783
|
+
switch (event.event) {
|
|
20784
|
+
case "thinking:start":
|
|
20785
|
+
chat.setStatus("\u{1F914} Thinking of next question...");
|
|
20786
|
+
break;
|
|
20787
|
+
case "thinking:end":
|
|
20788
|
+
chat.clearStatus();
|
|
20789
|
+
break;
|
|
20790
|
+
case "tool:start":
|
|
20791
|
+
chat.setStatus(`\u{1F527} ${event.toolName}...`);
|
|
20792
|
+
break;
|
|
20793
|
+
case "tool:end":
|
|
20794
|
+
chat.clearStatus();
|
|
20795
|
+
break;
|
|
20796
|
+
case "insight:discovered":
|
|
20797
|
+
chat.showInsight(`${event.field}: ${event.insight}`);
|
|
20798
|
+
break;
|
|
20799
|
+
}
|
|
20800
|
+
};
|
|
20801
|
+
setChatMode(true);
|
|
20802
|
+
chat.enter();
|
|
20803
|
+
chat.addMessage("system", `Starting interview for idea #${idea.issueNumber}: ${idea.topic}`);
|
|
20804
|
+
chat.addMessage("system", "The agent will ask Socratic questions to help develop your idea.");
|
|
20805
|
+
try {
|
|
20806
|
+
const result = await startInterview(idea, answerProvider, handleEvent);
|
|
20807
|
+
await saveResults(result, chat, parsed);
|
|
20808
|
+
} catch (error) {
|
|
20809
|
+
if (error instanceof Error) {
|
|
20810
|
+
chat.addMessage("error", error.message);
|
|
20811
|
+
}
|
|
20812
|
+
throw error;
|
|
20813
|
+
} finally {
|
|
20814
|
+
chat.destroy();
|
|
20815
|
+
setChatMode(false);
|
|
20816
|
+
}
|
|
20817
|
+
}
|
|
20818
|
+
async function saveResults(result, chat, issueNumber) {
|
|
20819
|
+
const durationSec = Math.round(result.durationMs / 1e3);
|
|
20820
|
+
const fieldList = result.updatedFields.length > 0 ? result.updatedFields.join(", ") : "none";
|
|
20821
|
+
chat.showQuestion(
|
|
20822
|
+
`Interview ${result.endedBy === "user" ? "ended" : "completed"} \u2014 ${result.transcript.length} questions in ${durationSec}s`,
|
|
20823
|
+
`Updated fields: ${fieldList}`,
|
|
20824
|
+
"summary",
|
|
20825
|
+
result.transcript.length
|
|
20826
|
+
);
|
|
20827
|
+
if (result.transcript.length > 0) {
|
|
20828
|
+
chat.setStatus("\u{1F4BE} Saving transcript...");
|
|
20829
|
+
await saveTranscript(issueNumber, result.transcript);
|
|
20830
|
+
}
|
|
20831
|
+
if (result.insights && Object.keys(result.insights).length > 0) {
|
|
20832
|
+
chat.setStatus("\u{1F4BE} Updating idea fields...");
|
|
20833
|
+
await updateIdeaFromInsights(issueNumber, result.insights);
|
|
20834
|
+
}
|
|
20835
|
+
chat.clearStatus();
|
|
20836
|
+
chat.showInsight("\u2705 Saved! Mark this idea as ready? (yes/no)");
|
|
20837
|
+
const response = await chat.promptInput();
|
|
20838
|
+
if (response.toLowerCase().startsWith("y")) {
|
|
20839
|
+
await updateIdea(issueNumber, { status: "ready" });
|
|
20840
|
+
chat.showInsight(`\u2705 Idea #${issueNumber} marked as ready`);
|
|
20841
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1500));
|
|
20842
|
+
}
|
|
20843
|
+
}
|
|
20844
|
+
|
|
20075
20845
|
// src/L1-infra/readline/readlinePromises.ts
|
|
20076
|
-
import { createInterface
|
|
20846
|
+
import { createInterface } from "readline/promises";
|
|
20077
20847
|
function createPromptInterface(options) {
|
|
20078
|
-
return
|
|
20848
|
+
return createInterface({
|
|
20079
20849
|
input: options?.input ?? process.stdin,
|
|
20080
20850
|
output: options?.output ?? process.stdout
|
|
20081
20851
|
});
|
|
@@ -21743,6 +22513,10 @@ program.command("chat").description("Interactive chat session with the schedule
|
|
|
21743
22513
|
program.command("doctor").description("Check all prerequisites and dependencies").action(async () => {
|
|
21744
22514
|
await runDoctor();
|
|
21745
22515
|
});
|
|
22516
|
+
program.command("ideate-start <issue-number>").description("Start an interactive session to develop a content idea").option("--mode <mode>", "Session mode: interview (default)", "interview").option("--progress", "Emit structured JSON interview events to stderr").action(async (issueNumber, opts) => {
|
|
22517
|
+
await runIdeateStart(issueNumber, opts);
|
|
22518
|
+
process.exit(0);
|
|
22519
|
+
});
|
|
21746
22520
|
program.command("ideate").description("Generate AI-powered content ideas using trend research").option("--topics <topics>", "Comma-separated seed topics").option("--count <n>", "Number of ideas to generate (default: 5)", "5").option("--output <dir>", "Ideas directory (default: ./ideas)").option("--brand <path>", "Brand config path (default: ./brand.json)").option("--list", "List existing ideas instead of generating").option("--status <status>", "Filter by status when listing (draft|ready|recorded|published)").option("--format <format>", "Output format: table (default) or json").option("--add", "Add a single idea (AI-researched by default, or --no-ai for direct)").option("--topic <topic>", "Idea topic/title (required with --add)").option("--hook <hook>", "Attention-grabbing hook (default: topic, --no-ai only)").option("--audience <audience>", "Target audience (default: developers, --no-ai only)").option("--platforms <platforms>", "Comma-separated platforms: tiktok,youtube,instagram,linkedin,x (--no-ai only)").option("--key-takeaway <takeaway>", "Core message the viewer should remember (--no-ai only)").option("--talking-points <points>", "Comma-separated talking points (--no-ai only)").option("--tags <tags>", "Comma-separated categorization tags (--no-ai only)").option("--publish-by <date>", "Publish deadline (ISO 8601 date, default: 14 days from now, --no-ai only)").option("--trend-context <context>", "Why this topic is timely (--no-ai only)").option("--no-ai", "Skip AI research agent \u2014 create directly from CLI flags + defaults").option("-p, --prompt <prompt>", 'Free-form prompt to guide idea generation (e.g., "Cover this article: https://...")').action(async (opts) => {
|
|
21747
22521
|
initConfig();
|
|
21748
22522
|
await runIdeate(opts);
|