octto 0.1.4 → 0.2.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,32 @@ 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
+
121
+ ### Environment Variables
122
+
123
+ | Variable | Description |
124
+ |----------|-------------|
125
+ | `OCTTO_PORT` | Override port (takes precedence over config file) |
126
+
127
+ For Docker workflows, set a fixed port:
128
+
129
+ ```bash
130
+ OCTTO_PORT=3000 opencode
131
+ ```
132
+
113
133
  ## Development
114
134
 
115
135
  ```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, OcttoConfig } from "./loader";
2
+ export { loadCustomConfig, resolvePort } from "./loader";
3
+ export { AgentOverrideSchema, OcttoConfigSchema, PortSchema } from "./schema";
@@ -1,8 +1,17 @@
1
1
  import type { AgentConfig } from "@opencode-ai/sdk";
2
2
  import { AGENTS } from "@/agents";
3
3
  export type { AgentOverride, OcttoConfig } from "./schema";
4
+ /**
5
+ * Resolve port from environment variable or config.
6
+ * Priority: OCTTO_PORT env var > config port > default (0 = random)
7
+ */
8
+ export declare function resolvePort(configPort?: number): number;
9
+ export interface CustomConfig {
10
+ agents: Record<AGENTS, AgentConfig>;
11
+ port: number;
12
+ }
4
13
  /**
5
14
  * Load user configuration and merge with plugin agents.
6
- * Returns merged agent configs with user overrides applied.
15
+ * Returns merged agent configs with user overrides applied, and resolved port.
7
16
  */
8
- export declare function loadCustomConfig(agents: Record<AGENTS, AgentConfig>, configDir?: string): Promise<Record<AGENTS, AgentConfig>>;
17
+ export declare function loadCustomConfig(agents: Record<AGENTS, AgentConfig>, configDir?: string): Promise<CustomConfig>;
@@ -38,6 +38,7 @@ 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>]>;
41
42
  export declare const OcttoConfigSchema: v.ObjectSchema<{
42
43
  readonly agents: v.OptionalSchema<v.RecordSchema<v.EnumSchema<typeof AGENTS, undefined>, Omit<v.ObjectSchema<{
43
44
  readonly model: v.StringSchema<undefined>;
@@ -77,6 +78,7 @@ export declare const OcttoConfigSchema: v.ObjectSchema<{
77
78
  readonly issue: v.ObjectIssue | v.StringIssue | v.NumberIssue | v.MinValueIssue<number, 0> | v.MaxValueIssue<number, 2> | v.IntegerIssue<number> | v.MinValueIssue<number, 1>;
78
79
  } | undefined;
79
80
  }, undefined>, undefined>;
81
+ 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>;
80
82
  }, undefined>;
81
83
  export type AgentOverride = v.InferOutput<typeof AgentOverrideSchema>;
82
84
  export type OcttoConfig = v.InferOutput<typeof OcttoConfigSchema>;
package/dist/index.js CHANGED
@@ -795,11 +795,25 @@ var AgentOverrideSchema = partial(object({
795
795
  temperature: pipe(number(), minValue(0), maxValue(2)),
796
796
  maxSteps: pipe(number(), integer(), minValue(1))
797
797
  }));
798
+ var PortSchema = pipe(number(), integer(), minValue(0), maxValue(65535));
798
799
  var OcttoConfigSchema = object({
799
- agents: optional(record(enum_(AGENTS), AgentOverrideSchema))
800
+ agents: optional(record(enum_(AGENTS), AgentOverrideSchema)),
801
+ port: optional(PortSchema)
800
802
  });
801
803
 
802
804
  // src/config/loader.ts
805
+ var OCTTO_PORT_ENV = "OCTTO_PORT";
806
+ var DEFAULT_PORT = 0;
807
+ function resolvePort(configPort) {
808
+ const envValue = process.env[OCTTO_PORT_ENV];
809
+ if (envValue !== undefined) {
810
+ const parsed = Number(envValue);
811
+ if (Number.isInteger(parsed) && parsed >= 0 && parsed <= 65535) {
812
+ return parsed;
813
+ }
814
+ }
815
+ return configPort ?? DEFAULT_PORT;
816
+ }
803
817
  var VALID_AGENT_NAMES = Object.values(AGENTS);
804
818
  function formatValidationErrors(issues) {
805
819
  return issues.map((issue) => {
@@ -858,14 +872,16 @@ async function load(configDir) {
858
872
  }
859
873
  async function loadCustomConfig(agents2, configDir) {
860
874
  const config = await load(configDir);
861
- if (!config?.agents) {
862
- return agents2;
863
- }
864
- const result = { ...agents2 };
865
- for (const [name, override] of Object.entries(config.agents)) {
866
- result[name] = { ...agents2[name], ...override };
875
+ const mergedAgents = { ...agents2 };
876
+ if (config?.agents) {
877
+ for (const [name, override] of Object.entries(config.agents)) {
878
+ mergedAgents[name] = { ...agents2[name], ...override };
879
+ }
867
880
  }
868
- return result;
881
+ return {
882
+ agents: mergedAgents,
883
+ port: resolvePort(config?.port)
884
+ };
869
885
  }
870
886
  // src/constants.ts
871
887
  var DEFAULT_ANSWER_TIMEOUT_MS = 300000;
@@ -2504,10 +2520,10 @@ function getHtmlBundle() {
2504
2520
  </html>`;
2505
2521
  }
2506
2522
  // src/session/server.ts
2507
- async function createServer(sessionId, store) {
2523
+ async function createServer(sessionId, store, configuredPort) {
2508
2524
  const htmlBundle = getHtmlBundle();
2509
2525
  const server = Bun.serve({
2510
- port: 0,
2526
+ port: configuredPort ?? 0,
2511
2527
  fetch(req, server2) {
2512
2528
  const url = new URL(req.url);
2513
2529
  if (url.pathname === "/ws") {
@@ -2665,7 +2681,7 @@ function createSessionStore(options = {}) {
2665
2681
  const store = {
2666
2682
  async startSession(input) {
2667
2683
  const sessionId = generateSessionId();
2668
- const { server, port } = await createServer(sessionId, store);
2684
+ const { server, port } = await createServer(sessionId, store, options.port);
2669
2685
  const url = `http://localhost:${port}`;
2670
2686
  const session = {
2671
2687
  id: sessionId,
@@ -16525,7 +16541,7 @@ function createOcttoTools(sessions, client) {
16525
16541
  // src/index.ts
16526
16542
  var Octto = async ({ client }) => {
16527
16543
  const customConfig = await loadCustomConfig(agents);
16528
- const sessions = createSessionStore();
16544
+ const sessions = createSessionStore({ port: customConfig.port });
16529
16545
  const tracked = new Map;
16530
16546
  const tools = createOcttoTools(sessions, client);
16531
16547
  const originalExecute = tools.start_session.execute;
@@ -16543,7 +16559,7 @@ var Octto = async ({ client }) => {
16543
16559
  return {
16544
16560
  tool: tools,
16545
16561
  config: async (config2) => {
16546
- config2.agent = { ...config2.agent, ...customConfig };
16562
+ config2.agent = { ...config2.agent, ...customConfig.agents };
16547
16563
  },
16548
16564
  event: async ({ event }) => {
16549
16565
  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.2.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",