octto 0.1.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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Opens the default browser to the specified URL.
3
+ * Detects platform and uses appropriate command.
4
+ */
5
+ export declare function openBrowser(url: string): Promise<void>;
@@ -0,0 +1,4 @@
1
+ export type { SessionStore, SessionStoreOptions } from "./sessions";
2
+ export { createSessionStore } from "./sessions";
3
+ export type { Answer, AskCodeAnswer, AskFileAnswer, AskImageAnswer, AskTextAnswer, BaseConfig, ConfirmAnswer, EmojiReactAnswer, PickManyAnswer, PickOneAnswer, QuestionAnswers, QuestionConfig, QuestionType, RankAnswer, RateAnswer, ReviewAnswer, ShowOptionsAnswer, SliderAnswer, ThumbsAnswer, } from "./types";
4
+ export { QUESTION_TYPES, QUESTIONS, STATUSES, WS_MESSAGES } from "./types";
@@ -0,0 +1,10 @@
1
+ import type { Server } from "bun";
2
+ import type { SessionStore } from "./sessions";
3
+ interface WsData {
4
+ sessionId: string;
5
+ }
6
+ export declare function createServer(sessionId: string, store: SessionStore): Promise<{
7
+ server: Server<WsData>;
8
+ port: number;
9
+ }>;
10
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { ServerWebSocket } from "bun";
2
+ import { type BaseConfig, type EndSessionOutput, type GetAnswerInput, type GetAnswerOutput, type GetNextAnswerInput, type GetNextAnswerOutput, type ListQuestionsOutput, type PushQuestionOutput, type QuestionType, type Session, type StartSessionInput, type StartSessionOutput, type WsClientMessage } from "./types";
3
+ export interface SessionStoreOptions {
4
+ /** Skip opening browser - useful for tests */
5
+ skipBrowser?: boolean;
6
+ }
7
+ export interface SessionStore {
8
+ startSession: (input: StartSessionInput) => Promise<StartSessionOutput>;
9
+ endSession: (sessionId: string) => Promise<EndSessionOutput>;
10
+ pushQuestion: (sessionId: string, type: QuestionType, config: BaseConfig) => PushQuestionOutput;
11
+ getAnswer: (input: GetAnswerInput) => Promise<GetAnswerOutput>;
12
+ getNextAnswer: (input: GetNextAnswerInput) => Promise<GetNextAnswerOutput>;
13
+ cancelQuestion: (questionId: string) => {
14
+ ok: boolean;
15
+ };
16
+ listQuestions: (sessionId?: string) => ListQuestionsOutput;
17
+ handleWsConnect: (sessionId: string, ws: ServerWebSocket<unknown>) => void;
18
+ handleWsDisconnect: (sessionId: string) => void;
19
+ handleWsMessage: (sessionId: string, message: WsClientMessage) => void;
20
+ getSession: (sessionId: string) => Session | undefined;
21
+ cleanup: () => Promise<void>;
22
+ }
23
+ export declare function createSessionStore(options?: SessionStoreOptions): SessionStore;
@@ -0,0 +1,219 @@
1
+ import type { ServerWebSocket } from "bun";
2
+ import type { AskCodeConfig, AskFileConfig, AskImageConfig, AskTextConfig, ConfirmConfig, EmojiReactConfig, PickManyConfig, PickOneConfig, RankConfig, RateConfig, ReviewSectionConfig, ShowDiffConfig, ShowOptionsConfig, ShowPlanConfig, SliderConfig, ThumbsConfig } from "../types";
3
+ export declare const STATUSES: {
4
+ readonly PENDING: "pending";
5
+ readonly ANSWERED: "answered";
6
+ readonly CANCELLED: "cancelled";
7
+ readonly TIMEOUT: "timeout";
8
+ readonly NONE_PENDING: "none_pending";
9
+ };
10
+ export type QuestionStatus = (typeof STATUSES)[Exclude<keyof typeof STATUSES, "NONE_PENDING">];
11
+ export interface Question {
12
+ id: string;
13
+ sessionId: string;
14
+ type: QuestionType;
15
+ config: BaseConfig;
16
+ status: QuestionStatus;
17
+ createdAt: Date;
18
+ answeredAt?: Date;
19
+ response?: Answer;
20
+ /** True if this answer was already returned via get_next_answer */
21
+ retrieved?: boolean;
22
+ }
23
+ export declare const QUESTIONS: {
24
+ readonly PICK_ONE: "pick_one";
25
+ readonly PICK_MANY: "pick_many";
26
+ readonly CONFIRM: "confirm";
27
+ readonly RANK: "rank";
28
+ readonly RATE: "rate";
29
+ readonly ASK_TEXT: "ask_text";
30
+ readonly ASK_IMAGE: "ask_image";
31
+ readonly ASK_FILE: "ask_file";
32
+ readonly ASK_CODE: "ask_code";
33
+ readonly SHOW_DIFF: "show_diff";
34
+ readonly SHOW_PLAN: "show_plan";
35
+ readonly SHOW_OPTIONS: "show_options";
36
+ readonly REVIEW_SECTION: "review_section";
37
+ readonly THUMBS: "thumbs";
38
+ readonly EMOJI_REACT: "emoji_react";
39
+ readonly SLIDER: "slider";
40
+ };
41
+ export type QuestionType = (typeof QUESTIONS)[keyof typeof QUESTIONS];
42
+ export declare const QUESTION_TYPES: ("pick_one" | "pick_many" | "confirm" | "rank" | "rate" | "ask_text" | "ask_image" | "ask_file" | "ask_code" | "show_diff" | "show_plan" | "show_options" | "review_section" | "thumbs" | "emoji_react" | "slider")[];
43
+ export interface PickOneAnswer {
44
+ selected: string;
45
+ }
46
+ export interface PickManyAnswer {
47
+ selected: string[];
48
+ }
49
+ export interface ConfirmAnswer {
50
+ choice: "yes" | "no" | "cancel";
51
+ }
52
+ export interface ThumbsAnswer {
53
+ choice: "up" | "down";
54
+ }
55
+ export interface EmojiReactAnswer {
56
+ emoji: string;
57
+ }
58
+ export interface AskTextAnswer {
59
+ text: string;
60
+ }
61
+ export interface SliderAnswer {
62
+ value: number;
63
+ }
64
+ export interface RankAnswer {
65
+ ranking: Array<{
66
+ id: string;
67
+ rank: number;
68
+ }>;
69
+ }
70
+ export interface RateAnswer {
71
+ ratings: Record<string, number>;
72
+ }
73
+ export interface AskCodeAnswer {
74
+ code: string;
75
+ }
76
+ export interface AskImageAnswer {
77
+ images: Array<{
78
+ name: string;
79
+ data: string;
80
+ type: string;
81
+ }>;
82
+ }
83
+ export interface AskFileAnswer {
84
+ files: Array<{
85
+ name: string;
86
+ data: string;
87
+ type: string;
88
+ }>;
89
+ }
90
+ export interface ReviewAnswer {
91
+ decision: string;
92
+ feedback?: string;
93
+ }
94
+ export interface ShowOptionsAnswer {
95
+ selected: string;
96
+ feedback?: string;
97
+ }
98
+ export type Answer = PickOneAnswer | PickManyAnswer | ConfirmAnswer | ThumbsAnswer | EmojiReactAnswer | AskTextAnswer | SliderAnswer | RankAnswer | RateAnswer | AskCodeAnswer | AskImageAnswer | AskFileAnswer | ReviewAnswer | ShowOptionsAnswer;
99
+ export interface QuestionAnswers {
100
+ [QUESTIONS.PICK_ONE]: PickOneAnswer;
101
+ [QUESTIONS.PICK_MANY]: PickManyAnswer;
102
+ [QUESTIONS.CONFIRM]: ConfirmAnswer;
103
+ [QUESTIONS.THUMBS]: ThumbsAnswer;
104
+ [QUESTIONS.EMOJI_REACT]: EmojiReactAnswer;
105
+ [QUESTIONS.ASK_TEXT]: AskTextAnswer;
106
+ [QUESTIONS.SLIDER]: SliderAnswer;
107
+ [QUESTIONS.RANK]: RankAnswer;
108
+ [QUESTIONS.RATE]: RateAnswer;
109
+ [QUESTIONS.ASK_CODE]: AskCodeAnswer;
110
+ [QUESTIONS.ASK_IMAGE]: AskImageAnswer;
111
+ [QUESTIONS.ASK_FILE]: AskFileAnswer;
112
+ [QUESTIONS.SHOW_DIFF]: ReviewAnswer;
113
+ [QUESTIONS.SHOW_PLAN]: ReviewAnswer;
114
+ [QUESTIONS.REVIEW_SECTION]: ReviewAnswer;
115
+ [QUESTIONS.SHOW_OPTIONS]: ShowOptionsAnswer;
116
+ }
117
+ export type QuestionConfig = PickOneConfig | PickManyConfig | ConfirmConfig | RankConfig | RateConfig | AskTextConfig | AskImageConfig | AskFileConfig | AskCodeConfig | ShowDiffConfig | ShowPlanConfig | ShowOptionsConfig | ReviewSectionConfig | ThumbsConfig | EmojiReactConfig | SliderConfig;
118
+ /** Config type for transit - accepts both strict QuestionConfig and loose objects */
119
+ export type BaseConfig = QuestionConfig | {
120
+ question?: string;
121
+ context?: string;
122
+ [key: string]: unknown;
123
+ };
124
+ export interface Session {
125
+ id: string;
126
+ title?: string;
127
+ port: number;
128
+ url: string;
129
+ createdAt: Date;
130
+ questions: Map<string, Question>;
131
+ wsConnected: boolean;
132
+ server?: ReturnType<typeof Bun.serve>;
133
+ wsClient?: ServerWebSocket<unknown>;
134
+ }
135
+ export interface InitialQuestion {
136
+ type: QuestionType;
137
+ config: BaseConfig;
138
+ }
139
+ export interface StartSessionInput {
140
+ title?: string;
141
+ /** Initial questions to display immediately when browser opens */
142
+ questions?: InitialQuestion[];
143
+ }
144
+ export interface StartSessionOutput {
145
+ session_id: string;
146
+ url: string;
147
+ /** IDs of initial questions if any were provided */
148
+ question_ids?: string[];
149
+ }
150
+ export interface EndSessionOutput {
151
+ ok: boolean;
152
+ }
153
+ export interface PushQuestionOutput {
154
+ question_id: string;
155
+ }
156
+ export interface GetAnswerInput {
157
+ question_id: string;
158
+ block?: boolean;
159
+ timeout?: number;
160
+ }
161
+ export interface GetAnswerOutput {
162
+ completed: boolean;
163
+ status: QuestionStatus;
164
+ response?: Answer;
165
+ reason?: "timeout" | "cancelled" | "pending";
166
+ }
167
+ export interface GetNextAnswerInput {
168
+ session_id: string;
169
+ block?: boolean;
170
+ timeout?: number;
171
+ }
172
+ export type AnswerStatus = (typeof STATUSES)[keyof typeof STATUSES];
173
+ export interface GetNextAnswerOutput {
174
+ completed: boolean;
175
+ question_id?: string;
176
+ question_type?: QuestionType;
177
+ status: AnswerStatus;
178
+ response?: Answer;
179
+ reason?: typeof STATUSES.TIMEOUT | typeof STATUSES.NONE_PENDING;
180
+ }
181
+ export interface ListQuestionsOutput {
182
+ questions: Array<{
183
+ id: string;
184
+ type: QuestionType;
185
+ status: QuestionStatus;
186
+ createdAt: string;
187
+ answeredAt?: string;
188
+ }>;
189
+ }
190
+ export declare const WS_MESSAGES: {
191
+ readonly QUESTION: "question";
192
+ readonly CANCEL: "cancel";
193
+ readonly END: "end";
194
+ readonly RESPONSE: "response";
195
+ readonly CONNECTED: "connected";
196
+ };
197
+ export interface WsQuestionMessage {
198
+ type: "question";
199
+ id: string;
200
+ questionType: QuestionType;
201
+ config: BaseConfig;
202
+ }
203
+ export interface WsCancelMessage {
204
+ type: "cancel";
205
+ id: string;
206
+ }
207
+ export interface WsEndMessage {
208
+ type: "end";
209
+ }
210
+ export interface WsResponseMessage {
211
+ type: "response";
212
+ id: string;
213
+ answer: Answer;
214
+ }
215
+ export interface WsConnectedMessage {
216
+ type: "connected";
217
+ }
218
+ export type WsServerMessage = WsQuestionMessage | WsCancelMessage | WsEndMessage;
219
+ export type WsClientMessage = WsResponseMessage | WsConnectedMessage;
@@ -0,0 +1,31 @@
1
+ export interface Waiters<K, T> {
2
+ register: (key: K, callback: (data: T) => void) => () => void;
3
+ notifyFirst: (key: K, data: T) => void;
4
+ notifyAll: (key: K, data: T) => void;
5
+ has: (key: K) => boolean;
6
+ count: (key: K) => number;
7
+ clear: (key: K) => void;
8
+ }
9
+ /**
10
+ * Create a waiter registry for async response handling.
11
+ * Each operation creates a new array rather than mutating in place.
12
+ *
13
+ * @typeParam K - Key type (e.g., string for question_id or session_id)
14
+ * @typeParam T - Data type passed to waiter callbacks
15
+ */
16
+ export declare function createWaiters<K, T>(): Waiters<K, T>;
17
+ /**
18
+ * Result of waiting for a response
19
+ */
20
+ export type WaitResult<T> = {
21
+ ok: true;
22
+ data: T;
23
+ } | {
24
+ ok: false;
25
+ reason: "timeout";
26
+ };
27
+ /**
28
+ * Wait for a response with timeout.
29
+ * Registers a waiter and returns a promise that resolves when notified or times out.
30
+ */
31
+ export declare function waitForResponse<K, T>(waiters: Waiters<K, T>, key: K, timeoutMs: number): Promise<WaitResult<T>>;
@@ -0,0 +1,3 @@
1
+ export * from "./persistence";
2
+ export * from "./store";
3
+ export * from "./types";
@@ -0,0 +1,8 @@
1
+ import type { BrainstormState } from "./types";
2
+ export interface StatePersistence {
3
+ save: (state: BrainstormState) => Promise<void>;
4
+ load: (sessionId: string) => Promise<BrainstormState | null>;
5
+ delete: (sessionId: string) => Promise<void>;
6
+ list: () => Promise<string[]>;
7
+ }
8
+ export declare function createStatePersistence(baseDir?: string): StatePersistence;
@@ -0,0 +1,14 @@
1
+ import type { Answer } from "@/session";
2
+ import { type BrainstormState, type Branch, type BranchQuestion, type CreateBranchInput } from "./types";
3
+ export interface StateStore {
4
+ createSession: (sessionId: string, request: string, branches: CreateBranchInput[]) => Promise<BrainstormState>;
5
+ getSession: (sessionId: string) => Promise<BrainstormState | null>;
6
+ setBrowserSessionId: (sessionId: string, browserSessionId: string) => Promise<void>;
7
+ addQuestionToBranch: (sessionId: string, branchId: string, question: BranchQuestion) => Promise<BranchQuestion>;
8
+ recordAnswer: (sessionId: string, questionId: string, answer: Answer) => Promise<void>;
9
+ completeBranch: (sessionId: string, branchId: string, finding: string) => Promise<void>;
10
+ getNextExploringBranch: (sessionId: string) => Promise<Branch | null>;
11
+ isSessionComplete: (sessionId: string) => Promise<boolean>;
12
+ deleteSession: (sessionId: string) => Promise<void>;
13
+ }
14
+ export declare function createStateStore(baseDir?: string): StateStore;
@@ -0,0 +1,43 @@
1
+ import type { Answer, BaseConfig, QuestionType } from "@/session";
2
+ export declare const BRANCH_STATUSES: {
3
+ readonly EXPLORING: "exploring";
4
+ readonly DONE: "done";
5
+ };
6
+ export type BranchStatus = (typeof BRANCH_STATUSES)[keyof typeof BRANCH_STATUSES];
7
+ export interface BranchQuestion {
8
+ id: string;
9
+ type: QuestionType;
10
+ text: string;
11
+ config: BaseConfig;
12
+ answer?: Answer;
13
+ answeredAt?: number;
14
+ }
15
+ export interface Branch {
16
+ id: string;
17
+ scope: string;
18
+ status: BranchStatus;
19
+ questions: BranchQuestion[];
20
+ finding: string | null;
21
+ }
22
+ export interface BrainstormState {
23
+ session_id: string;
24
+ browser_session_id: string | null;
25
+ request: string;
26
+ created_at: number;
27
+ updated_at: number;
28
+ branches: Record<string, Branch>;
29
+ branch_order: string[];
30
+ }
31
+ export interface CreateBranchInput {
32
+ id: string;
33
+ scope: string;
34
+ }
35
+ export interface BranchProbeResult {
36
+ done: boolean;
37
+ reason: string;
38
+ finding?: string;
39
+ question?: {
40
+ type: QuestionType;
41
+ config: BaseConfig;
42
+ };
43
+ }
@@ -0,0 +1,3 @@
1
+ import type { SessionStore } from "@/session";
2
+ import type { OcttoTools, OpencodeClient } from "./types";
3
+ export declare function createBrainstormTools(sessions: SessionStore, client: OpencodeClient): OcttoTools;
@@ -0,0 +1,2 @@
1
+ import type { Answer, QuestionType } from "@/session";
2
+ export declare function extractAnswerSummary(type: QuestionType, answer: Answer): string;
@@ -0,0 +1,16 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
2
+ import type { BaseConfig, QuestionType, SessionStore } from "@/session";
3
+ import type { OcttoTool, OcttoTools } from "./types";
4
+ type ArgsSchema = Parameters<typeof tool>[0]["args"];
5
+ interface QuestionToolConfig<T> {
6
+ type: QuestionType;
7
+ description: string;
8
+ args: ArgsSchema;
9
+ validate?: (args: T) => string | null;
10
+ toConfig: (args: T) => BaseConfig;
11
+ }
12
+ export declare function createQuestionToolFactory(sessions: SessionStore): <T extends {
13
+ session_id: string;
14
+ }>(config: QuestionToolConfig<T>) => OcttoTool;
15
+ export declare function createPushQuestionTool(sessions: SessionStore): OcttoTools;
16
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { BrainstormState, Branch } from "@/state";
2
+ export declare function formatBranchFinding(branch: Branch): string;
3
+ export declare function formatBranchStatus(branch: Branch): string;
4
+ export declare function formatFindings(state: BrainstormState): string;
5
+ export declare function formatFindingsList(state: BrainstormState): string;
6
+ export declare function formatQASummary(branch: Branch): string;
@@ -0,0 +1,3 @@
1
+ import type { SessionStore } from "@/session";
2
+ import type { OcttoTools, OpencodeClient } from "./types";
3
+ export declare function createOcttoTools(sessions: SessionStore, client: OpencodeClient): OcttoTools;
@@ -0,0 +1,4 @@
1
+ import type { Answer, SessionStore } from "@/session";
2
+ import { type StateStore } from "@/state";
3
+ import type { OpencodeClient } from "./types";
4
+ export declare function processAnswer(stateStore: StateStore, sessions: SessionStore, sessionId: string, browserSessionId: string, questionId: string, answer: Answer, client: OpencodeClient): Promise<void>;
@@ -0,0 +1,3 @@
1
+ import type { SessionStore } from "@/session";
2
+ import type { OcttoTools } from "./types";
3
+ export declare function createQuestionTools(sessions: SessionStore): OcttoTools;
@@ -0,0 +1,3 @@
1
+ import { type SessionStore } from "@/session";
2
+ import type { OcttoTools } from "./types";
3
+ export declare function createResponseTools(sessions: SessionStore): OcttoTools;
@@ -0,0 +1,3 @@
1
+ import type { SessionStore } from "@/session";
2
+ import type { OcttoTools } from "./types";
3
+ export declare function createSessionTools(sessions: SessionStore): OcttoTools;
@@ -0,0 +1,9 @@
1
+ import type { ToolContext } from "@opencode-ai/plugin/tool";
2
+ import type { createOpencodeClient } from "@opencode-ai/sdk";
3
+ export interface OcttoTool {
4
+ description: string;
5
+ args: any;
6
+ execute: (args: any, context: ToolContext) => Promise<string>;
7
+ }
8
+ export type OcttoTools = Record<string, OcttoTool>;
9
+ export type OpencodeClient = ReturnType<typeof createOpencodeClient>;
@@ -0,0 +1,2 @@
1
+ export declare function generateSessionId(): string;
2
+ export declare function generateQuestionId(): string;