octto 0.1.4 → 0.3.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/README.md CHANGED
@@ -104,12 +104,60 @@ Optional `~/.config/opencode/octto.json`:
104
104
 
105
105
  ```json
106
106
  {
107
+ "port": 3000,
107
108
  "agents": {
108
109
  "probe": { "model": "anthropic/claude-sonnet-4" }
109
110
  }
110
111
  }
111
112
  ```
112
113
 
114
+ ### Options
115
+
116
+ | Option | Type | Default | Description |
117
+ |--------|------|---------|-------------|
118
+ | `port` | number | `0` (random) | Fixed port for the browser UI server |
119
+ | `agents` | object | - | Override agent models/settings |
120
+ | `fragments` | object | - | Custom instructions injected into agent prompts |
121
+
122
+ ### Fragments
123
+
124
+ Inject custom instructions into agent prompts. Useful for customizing agent behavior per-project or globally.
125
+
126
+ **Global config** (`~/.config/opencode/octto.json`):
127
+
128
+ ```json
129
+ {
130
+ "fragments": {
131
+ "octto": ["Always suggest 3 implementation approaches"],
132
+ "probe": ["Include emoji in every question"],
133
+ "bootstrapper": ["Focus on technical feasibility"]
134
+ }
135
+ }
136
+ ```
137
+
138
+ **Project config** (`.octto/fragments.json` in your project root):
139
+
140
+ ```json
141
+ {
142
+ "octto": ["This project uses React - focus on component patterns"],
143
+ "probe": ["Ask about testing strategy for each feature"]
144
+ }
145
+ ```
146
+
147
+ Fragments are merged: global fragments load first, project fragments append. Each fragment becomes a bullet point in a `<user-instructions>` block prepended to the agent's system prompt.
148
+
149
+ ### Environment Variables
150
+
151
+ | Variable | Description |
152
+ |----------|-------------|
153
+ | `OCTTO_PORT` | Override port (takes precedence over config file) |
154
+
155
+ For Docker workflows, set a fixed port:
156
+
157
+ ```bash
158
+ OCTTO_PORT=3000 opencode
159
+ ```
160
+
113
161
  ## Development
114
162
 
115
163
  ```bash
@@ -1,3 +1,3 @@
1
- export { loadCustomConfig } from "./loader";
2
- export type { AgentOverride, OcttoConfig } from "./schema";
3
- export { AgentOverrideSchema, OcttoConfigSchema } from "./schema";
1
+ export type { AgentOverride, CustomConfig, Fragments, OcttoConfig } from "./loader";
2
+ export { loadCustomConfig, resolvePort } from "./loader";
3
+ export { AgentOverrideSchema, FragmentsSchema, OcttoConfigSchema, PortSchema } from "./schema";
@@ -1,8 +1,19 @@
1
1
  import type { AgentConfig } from "@opencode-ai/sdk";
2
2
  import { AGENTS } from "@/agents";
3
- export type { AgentOverride, OcttoConfig } from "./schema";
3
+ import { type Fragments } from "./schema";
4
+ export type { AgentOverride, Fragments, OcttoConfig } from "./schema";
5
+ /**
6
+ * Resolve port from environment variable or config.
7
+ * Priority: OCTTO_PORT env var > config port > default (0 = random)
8
+ */
9
+ export declare function resolvePort(configPort?: number): number;
10
+ export interface CustomConfig {
11
+ agents: Record<AGENTS, AgentConfig>;
12
+ port: number;
13
+ fragments: Fragments;
14
+ }
4
15
  /**
5
16
  * Load user configuration and merge with plugin agents.
6
- * Returns merged agent configs with user overrides applied.
17
+ * Returns merged agent configs with user overrides applied, and resolved port.
7
18
  */
8
- export declare function loadCustomConfig(agents: Record<AGENTS, AgentConfig>, configDir?: string): Promise<Record<AGENTS, AgentConfig>>;
19
+ export declare function loadCustomConfig(agents: Record<AGENTS, AgentConfig>, configDir?: string): Promise<CustomConfig>;
@@ -38,6 +38,8 @@ export declare const AgentOverrideSchema: Omit<v.ObjectSchema<{
38
38
  readonly issue: v.ObjectIssue | v.StringIssue | v.NumberIssue | v.MinValueIssue<number, 0> | v.MaxValueIssue<number, 2> | v.IntegerIssue<number> | v.MinValueIssue<number, 1>;
39
39
  } | undefined;
40
40
  };
41
+ export declare const PortSchema: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>, v.MaxValueAction<number, 65535, undefined>]>;
42
+ export declare const FragmentsSchema: v.OptionalSchema<v.RecordSchema<v.EnumSchema<typeof AGENTS, undefined>, v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, undefined>;
41
43
  export declare const OcttoConfigSchema: v.ObjectSchema<{
42
44
  readonly agents: v.OptionalSchema<v.RecordSchema<v.EnumSchema<typeof AGENTS, undefined>, Omit<v.ObjectSchema<{
43
45
  readonly model: v.StringSchema<undefined>;
@@ -77,6 +79,9 @@ export declare const OcttoConfigSchema: v.ObjectSchema<{
77
79
  readonly issue: v.ObjectIssue | v.StringIssue | v.NumberIssue | v.MinValueIssue<number, 0> | v.MaxValueIssue<number, 2> | v.IntegerIssue<number> | v.MinValueIssue<number, 1>;
78
80
  } | undefined;
79
81
  }, undefined>, undefined>;
82
+ readonly port: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>, v.MaxValueAction<number, 65535, undefined>]>, undefined>;
83
+ readonly fragments: v.OptionalSchema<v.RecordSchema<v.EnumSchema<typeof AGENTS, undefined>, v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, undefined>;
80
84
  }, undefined>;
81
85
  export type AgentOverride = v.InferOutput<typeof AgentOverrideSchema>;
86
+ export type Fragments = v.InferOutput<typeof FragmentsSchema>;
82
87
  export type OcttoConfig = v.InferOutput<typeof OcttoConfigSchema>;
@@ -0,0 +1,37 @@
1
+ type FragmentsRecord = Record<string, string[]> | undefined;
2
+ /**
3
+ * Format fragments array as an XML block to prepend to agent prompts.
4
+ */
5
+ export declare function formatFragmentsBlock(fragments: string[] | undefined): string;
6
+ /**
7
+ * Merge global and project fragments.
8
+ * Global fragments come first, project fragments append.
9
+ */
10
+ export declare function mergeFragments(global: FragmentsRecord, project: FragmentsRecord): Record<string, string[]>;
11
+ /**
12
+ * Load project-level fragments from .octto/fragments.json
13
+ */
14
+ export declare function loadProjectFragments(projectDir: string): Promise<Record<string, string[]> | undefined>;
15
+ /**
16
+ * Calculate Levenshtein distance between two strings.
17
+ * Used for suggesting similar agent names for typos.
18
+ */
19
+ export declare function levenshteinDistance(a: string, b: string): number;
20
+ /**
21
+ * Warn about unknown agent names in fragments config.
22
+ * Suggests similar valid agent names for likely typos.
23
+ */
24
+ export declare function warnUnknownAgents(fragments: Record<string, string[]> | undefined): void;
25
+ export interface FragmentInjectorContext {
26
+ projectDir: string;
27
+ }
28
+ /**
29
+ * Create a fragment injector that can modify agent system prompts.
30
+ * Returns merged fragments from global config and project config.
31
+ */
32
+ export declare function createFragmentInjector(ctx: FragmentInjectorContext, globalFragments: FragmentsRecord): Promise<Record<string, string[]>>;
33
+ /**
34
+ * Get the system prompt prefix for a specific agent.
35
+ */
36
+ export declare function getAgentSystemPromptPrefix(fragments: Record<string, string[]>, agentName: string): string;
37
+ export {};
@@ -0,0 +1 @@
1
+ export { createFragmentInjector, type FragmentInjectorContext, formatFragmentsBlock, getAgentSystemPromptPrefix, levenshteinDistance, loadProjectFragments, mergeFragments, warnUnknownAgents, } from "./fragment-injector";
package/dist/index.js CHANGED
@@ -509,6 +509,58 @@ function getFallback(schema, dataset, config$1) {
509
509
  function getDefault(schema, dataset, config$1) {
510
510
  return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
511
511
  }
512
+ function array(item, message$1) {
513
+ return {
514
+ kind: "schema",
515
+ type: "array",
516
+ reference: array,
517
+ expects: "Array",
518
+ async: false,
519
+ item,
520
+ message: message$1,
521
+ get "~standard"() {
522
+ return /* @__PURE__ */ _getStandardProps(this);
523
+ },
524
+ "~run"(dataset, config$1) {
525
+ const input = dataset.value;
526
+ if (Array.isArray(input)) {
527
+ dataset.typed = true;
528
+ dataset.value = [];
529
+ for (let key = 0;key < input.length; key++) {
530
+ const value$1 = input[key];
531
+ const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
532
+ if (itemDataset.issues) {
533
+ const pathItem = {
534
+ type: "array",
535
+ origin: "value",
536
+ input,
537
+ key,
538
+ value: value$1
539
+ };
540
+ for (const issue of itemDataset.issues) {
541
+ if (issue.path)
542
+ issue.path.unshift(pathItem);
543
+ else
544
+ issue.path = [pathItem];
545
+ dataset.issues?.push(issue);
546
+ }
547
+ if (!dataset.issues)
548
+ dataset.issues = itemDataset.issues;
549
+ if (config$1.abortEarly) {
550
+ dataset.typed = false;
551
+ break;
552
+ }
553
+ }
554
+ if (!itemDataset.typed)
555
+ dataset.typed = false;
556
+ dataset.value.push(itemDataset.value);
557
+ }
558
+ } else
559
+ _addIssue(this, "type", dataset, config$1);
560
+ return dataset;
561
+ }
562
+ };
563
+ }
512
564
  function enum_(enum__, message$1) {
513
565
  const options = [];
514
566
  for (const key in enum__)
@@ -795,11 +847,27 @@ var AgentOverrideSchema = partial(object({
795
847
  temperature: pipe(number(), minValue(0), maxValue(2)),
796
848
  maxSteps: pipe(number(), integer(), minValue(1))
797
849
  }));
850
+ var PortSchema = pipe(number(), integer(), minValue(0), maxValue(65535));
851
+ var FragmentsSchema = optional(record(enum_(AGENTS), array(string())));
798
852
  var OcttoConfigSchema = object({
799
- agents: optional(record(enum_(AGENTS), AgentOverrideSchema))
853
+ agents: optional(record(enum_(AGENTS), AgentOverrideSchema)),
854
+ port: optional(PortSchema),
855
+ fragments: FragmentsSchema
800
856
  });
801
857
 
802
858
  // src/config/loader.ts
859
+ var OCTTO_PORT_ENV = "OCTTO_PORT";
860
+ var DEFAULT_PORT = 0;
861
+ function resolvePort(configPort) {
862
+ const envValue = process.env[OCTTO_PORT_ENV];
863
+ if (envValue !== undefined) {
864
+ const parsed = Number(envValue);
865
+ if (Number.isInteger(parsed) && parsed >= 0 && parsed <= 65535) {
866
+ return parsed;
867
+ }
868
+ }
869
+ return configPort ?? DEFAULT_PORT;
870
+ }
803
871
  var VALID_AGENT_NAMES = Object.values(AGENTS);
804
872
  function formatValidationErrors(issues) {
805
873
  return issues.map((issue) => {
@@ -858,15 +926,125 @@ async function load(configDir) {
858
926
  }
859
927
  async function loadCustomConfig(agents2, configDir) {
860
928
  const config = await load(configDir);
861
- if (!config?.agents) {
862
- return agents2;
929
+ const mergedAgents = { ...agents2 };
930
+ if (config?.agents) {
931
+ for (const [name, override] of Object.entries(config.agents)) {
932
+ mergedAgents[name] = { ...agents2[name], ...override };
933
+ }
934
+ }
935
+ return {
936
+ agents: mergedAgents,
937
+ port: resolvePort(config?.port),
938
+ fragments: config?.fragments
939
+ };
940
+ }
941
+ // src/hooks/fragment-injector.ts
942
+ import { readFile as readFile2 } from "fs/promises";
943
+ import { join as join2 } from "path";
944
+ var VALID_AGENT_NAMES2 = Object.values(AGENTS);
945
+ var ProjectFragmentsSchema = record(string(), array(string()));
946
+ function formatFragmentsBlock(fragments) {
947
+ if (!fragments || fragments.length === 0) {
948
+ return "";
949
+ }
950
+ const bulletPoints = fragments.map((f) => `- ${f}`).join(`
951
+ `);
952
+ return `<user-instructions>
953
+ ${bulletPoints}
954
+ </user-instructions>
955
+
956
+ `;
957
+ }
958
+ function mergeFragments(global, project) {
959
+ const result = {};
960
+ if (global) {
961
+ for (const [agent4, frags] of Object.entries(global)) {
962
+ result[agent4] = [...frags];
963
+ }
863
964
  }
864
- const result = { ...agents2 };
865
- for (const [name, override] of Object.entries(config.agents)) {
866
- result[name] = { ...agents2[name], ...override };
965
+ if (project) {
966
+ for (const [agent4, frags] of Object.entries(project)) {
967
+ if (result[agent4]) {
968
+ result[agent4].push(...frags);
969
+ } else {
970
+ result[agent4] = [...frags];
971
+ }
972
+ }
867
973
  }
868
974
  return result;
869
975
  }
976
+ async function loadProjectFragments(projectDir) {
977
+ const fragmentsPath = join2(projectDir, ".octto", "fragments.json");
978
+ try {
979
+ const content = await readFile2(fragmentsPath, "utf-8");
980
+ const parsed = JSON.parse(content);
981
+ const result = safeParse(ProjectFragmentsSchema, parsed);
982
+ if (!result.success) {
983
+ console.warn(`[octto] Invalid fragments.json schema in ${fragmentsPath}`);
984
+ return;
985
+ }
986
+ return result.output;
987
+ } catch {
988
+ return;
989
+ }
990
+ }
991
+ function levenshteinDistance(a, b) {
992
+ if (a.length === 0)
993
+ return b.length;
994
+ if (b.length === 0)
995
+ return a.length;
996
+ const matrix = [];
997
+ for (let i = 0;i <= b.length; i++) {
998
+ matrix[i] = [i];
999
+ }
1000
+ for (let j = 0;j <= a.length; j++) {
1001
+ matrix[0][j] = j;
1002
+ }
1003
+ for (let i = 1;i <= b.length; i++) {
1004
+ for (let j = 1;j <= a.length; j++) {
1005
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
1006
+ matrix[i][j] = matrix[i - 1][j - 1];
1007
+ } else {
1008
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
1009
+ }
1010
+ }
1011
+ }
1012
+ return matrix[b.length][a.length];
1013
+ }
1014
+ function warnUnknownAgents(fragments) {
1015
+ if (!fragments)
1016
+ return;
1017
+ for (const agentName of Object.keys(fragments)) {
1018
+ if (VALID_AGENT_NAMES2.includes(agentName)) {
1019
+ continue;
1020
+ }
1021
+ let closest;
1022
+ let minDistance = Infinity;
1023
+ for (const validName of VALID_AGENT_NAMES2) {
1024
+ const distance = levenshteinDistance(agentName, validName);
1025
+ if (distance < minDistance && distance <= 3) {
1026
+ minDistance = distance;
1027
+ closest = validName;
1028
+ }
1029
+ }
1030
+ let message = `[octto] Unknown agent "${agentName}" in fragments config.`;
1031
+ if (closest) {
1032
+ message += ` Did you mean "${closest}"?`;
1033
+ }
1034
+ message += ` Valid agents: ${VALID_AGENT_NAMES2.join(", ")}`;
1035
+ console.warn(message);
1036
+ }
1037
+ }
1038
+ async function createFragmentInjector(ctx, globalFragments) {
1039
+ const projectFragments = await loadProjectFragments(ctx.projectDir);
1040
+ const merged = mergeFragments(globalFragments, projectFragments);
1041
+ warnUnknownAgents(globalFragments);
1042
+ warnUnknownAgents(projectFragments);
1043
+ return merged;
1044
+ }
1045
+ function getAgentSystemPromptPrefix(fragments, agentName) {
1046
+ return formatFragmentsBlock(fragments[agentName]);
1047
+ }
870
1048
  // src/constants.ts
871
1049
  var DEFAULT_ANSWER_TIMEOUT_MS = 300000;
872
1050
 
@@ -2504,10 +2682,10 @@ function getHtmlBundle() {
2504
2682
  </html>`;
2505
2683
  }
2506
2684
  // src/session/server.ts
2507
- async function createServer(sessionId, store) {
2685
+ async function createServer(sessionId, store, configuredPort) {
2508
2686
  const htmlBundle = getHtmlBundle();
2509
2687
  const server = Bun.serve({
2510
- port: 0,
2688
+ port: configuredPort ?? 0,
2511
2689
  fetch(req, server2) {
2512
2690
  const url = new URL(req.url);
2513
2691
  if (url.pathname === "/ws") {
@@ -2665,7 +2843,7 @@ function createSessionStore(options = {}) {
2665
2843
  const store = {
2666
2844
  async startSession(input) {
2667
2845
  const sessionId = generateSessionId();
2668
- const { server, port } = await createServer(sessionId, store);
2846
+ const { server, port } = await createServer(sessionId, store, options.port);
2669
2847
  const url = `http://localhost:${port}`;
2670
2848
  const session = {
2671
2849
  id: sessionId,
@@ -3085,7 +3263,7 @@ __export(exports_external, {
3085
3263
  bigint: () => bigint2,
3086
3264
  base64url: () => base64url2,
3087
3265
  base64: () => base642,
3088
- array: () => array,
3266
+ array: () => array2,
3089
3267
  any: () => any,
3090
3268
  _function: () => _function,
3091
3269
  _default: () => _default2,
@@ -3581,8 +3759,8 @@ function getEnumValues(entries) {
3581
3759
  const values = Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
3582
3760
  return values;
3583
3761
  }
3584
- function joinValues(array, separator = "|") {
3585
- return array.map((val) => stringifyPrimitive(val)).join(separator);
3762
+ function joinValues(array2, separator = "|") {
3763
+ return array2.map((val) => stringifyPrimitive(val)).join(separator);
3586
3764
  }
3587
3765
  function jsonStringifyReplacer(_, value) {
3588
3766
  if (typeof value === "bigint")
@@ -14270,7 +14448,7 @@ var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
14270
14448
  inst.nullable = () => nullable(inst);
14271
14449
  inst.nullish = () => optional2(nullable(inst));
14272
14450
  inst.nonoptional = (params) => nonoptional(inst, params);
14273
- inst.array = () => array(inst);
14451
+ inst.array = () => array2(inst);
14274
14452
  inst.or = (arg) => union([inst, arg]);
14275
14453
  inst.and = (arg) => intersection(inst, arg);
14276
14454
  inst.transform = (tx) => pipe2(inst, transform(tx));
@@ -14691,7 +14869,7 @@ var ZodArray = /* @__PURE__ */ $constructor("ZodArray", (inst, def) => {
14691
14869
  inst.length = (len, params) => inst.check(_length(len, params));
14692
14870
  inst.unwrap = () => inst.element;
14693
14871
  });
14694
- function array(element, params) {
14872
+ function array2(element, params) {
14695
14873
  return _array(ZodArray, element, params);
14696
14874
  }
14697
14875
  function keyof(schema) {
@@ -15153,7 +15331,7 @@ var ZodFunction = /* @__PURE__ */ $constructor("ZodFunction", (inst, def) => {
15153
15331
  function _function(params) {
15154
15332
  return new ZodFunction({
15155
15333
  type: "function",
15156
- input: Array.isArray(params?.input) ? tuple(params?.input) : params?.input ?? array(unknown()),
15334
+ input: Array.isArray(params?.input) ? tuple(params?.input) : params?.input ?? array2(unknown()),
15157
15335
  output: params?.output ?? unknown()
15158
15336
  });
15159
15337
  }
@@ -15197,7 +15375,7 @@ var stringbool = (...args) => _stringbool({
15197
15375
  }, ...args);
15198
15376
  function json(params) {
15199
15377
  const jsonSchema = lazy(() => {
15200
- return union([string3(params), number3(), boolean2(), _null3(), array(jsonSchema), record2(string3(), jsonSchema)]);
15378
+ return union([string3(params), number3(), boolean2(), _null3(), array2(jsonSchema), record2(string3(), jsonSchema)]);
15201
15379
  });
15202
15380
  return jsonSchema;
15203
15381
  }
@@ -15263,7 +15441,7 @@ tool.schema = exports_external;
15263
15441
 
15264
15442
  // src/state/persistence.ts
15265
15443
  import { existsSync, mkdirSync, readdirSync, rmSync } from "fs";
15266
- import { join as join2 } from "path";
15444
+ import { join as join3 } from "path";
15267
15445
  function validateSessionId(sessionId) {
15268
15446
  if (!/^[a-zA-Z0-9_-]+$/.test(sessionId)) {
15269
15447
  throw new Error(`Invalid session ID: ${sessionId}`);
@@ -15272,7 +15450,7 @@ function validateSessionId(sessionId) {
15272
15450
  function createStatePersistence(baseDir = ".brainstorm") {
15273
15451
  function getFilePath(sessionId) {
15274
15452
  validateSessionId(sessionId);
15275
- return join2(baseDir, `${sessionId}.json`);
15453
+ return join3(baseDir, `${sessionId}.json`);
15276
15454
  }
15277
15455
  function ensureDir() {
15278
15456
  if (!existsSync(baseDir)) {
@@ -16523,9 +16701,17 @@ function createOcttoTools(sessions, client) {
16523
16701
  }
16524
16702
 
16525
16703
  // src/index.ts
16526
- var Octto = async ({ client }) => {
16704
+ var Octto = async ({ client, directory }) => {
16527
16705
  const customConfig = await loadCustomConfig(agents);
16528
- const sessions = createSessionStore();
16706
+ const fragments = await createFragmentInjector({ projectDir: directory }, customConfig.fragments);
16707
+ for (const agentName of Object.values(AGENTS)) {
16708
+ const prefix = getAgentSystemPromptPrefix(fragments, agentName);
16709
+ if (prefix && customConfig.agents[agentName]?.prompt) {
16710
+ customConfig.agents[agentName].prompt = prefix + customConfig.agents[agentName].prompt;
16711
+ }
16712
+ }
16713
+ warnUnknownAgents(customConfig.fragments);
16714
+ const sessions = createSessionStore({ port: customConfig.port });
16529
16715
  const tracked = new Map;
16530
16716
  const tools = createOcttoTools(sessions, client);
16531
16717
  const originalExecute = tools.start_session.execute;
@@ -16543,7 +16729,7 @@ var Octto = async ({ client }) => {
16543
16729
  return {
16544
16730
  tool: tools,
16545
16731
  config: async (config2) => {
16546
- config2.agent = { ...config2.agent, ...customConfig };
16732
+ config2.agent = { ...config2.agent, ...customConfig.agents };
16547
16733
  },
16548
16734
  event: async ({ event }) => {
16549
16735
  if (event.type !== "session.deleted")
@@ -3,7 +3,7 @@ import type { SessionStore } from "./sessions";
3
3
  interface WsData {
4
4
  sessionId: string;
5
5
  }
6
- export declare function createServer(sessionId: string, store: SessionStore): Promise<{
6
+ export declare function createServer(sessionId: string, store: SessionStore, configuredPort?: number): Promise<{
7
7
  server: Server<WsData>;
8
8
  port: number;
9
9
  }>;
@@ -3,6 +3,8 @@ import { type BaseConfig, type EndSessionOutput, type GetAnswerInput, type GetAn
3
3
  export interface SessionStoreOptions {
4
4
  /** Skip opening browser - useful for tests */
5
5
  skipBrowser?: boolean;
6
+ /** Fixed port for the server (0 = random available port) */
7
+ port?: number;
6
8
  }
7
9
  export interface SessionStore {
8
10
  startSession: (input: StartSessionInput) => Promise<StartSessionOutput>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octto",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "description": "OpenCode plugin that turns rough ideas into designs through branch-based exploration",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",