opencode-q 1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dev3am
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ja.md ADDED
@@ -0,0 +1,73 @@
1
+ # opencode-q
2
+
3
+ **OpenCode用プロンプトキュープラグイン** — OpenCode を起動し `http://localhost:4321` を開いて、セッションごとのプロンプトキューを ToDo リストのように管理します。プロンプトを事前に積んでおき、AI に1つずつ送信し、ステータスをリアルタイムで追跡します。
4
+
5
+ [English](https://github.com/dev3am/opencode-q/blob/main/README.md) | [한국어](https://github.com/dev3am/opencode-q/blob/main/README.ko.md) | [中文](https://github.com/dev3am/opencode-q/blob/main/README.zh.md)
6
+
7
+ ---
8
+
9
+ ## 何ができるか
10
+
11
+ opencode-q は **Web 専用** の OpenCode プラグインです。CLI も TUI コマンドもありません — すべて1つの Web UI から操作します。
12
+
13
+ - **すべてのプロジェクトを1つの Web で** — `http://localhost:4321` に、起動中のすべての OpenCode インスタンスが集まります。
14
+ - **プロジェクト別・セッション別のキュー** — 各会話(セッション)が独立したキューを持ちます。
15
+ - **ToDo スタイルのステータス追跡** — 各項目は `queued → pending → sent → done`(または `failed`)を経ます。
16
+ - **手動・1つずつ** — キューのプロンプトを明示的に送信し、前のものが終わってから次を送ります。常にユーザーが流れを制御します。
17
+ - キュー項目の **並び替え・編集・削除**、失敗項目の **再送信**。
18
+ - **堅牢な設計** — すべての状態をディスクに保存するため、OpenCode のウィンドウが開閉してもシステムは動作し続けます。
19
+
20
+ ## インストール
21
+
22
+ ### 前提条件
23
+
24
+ - [OpenCode](https://opencode.ai) 1.x([Bun](https://bun.sh) 上で動作し、プラグインも Bun を使用します)
25
+
26
+ ### npm
27
+
28
+ ```bash
29
+ npm install -g opencode-q
30
+ ```
31
+
32
+ インストール時に postinstall ステップで自動的に `~/.config/opencode/plugins/` に登録されます。OpenCode を再起動するだけで使用できます。
33
+
34
+ > **注意:** プラグインはグローバルパス(`~/.config/opencode/plugins/`)に登録されるため、グローバルインストール(`-g`)が必要です。プロジェクト単位のインストールはサポートされていません。
35
+
36
+ ## 使い方
37
+
38
+ 1. 任意のプロジェクトで OpenCode を起動します(複数のプロジェクトで同時に起動しても構いません)。
39
+ 2. ブラウザで **`http://localhost:4321`** を開きます。
40
+ 3. サイドバーでプロジェクトを選び、セッションタブを選択して:
41
+ - プロンプトを **追加** — `queued` として表示されます。
42
+ - キュー項目を **送信** — `pending` → AI 作業中は `sent` → AI 完了時に `done` に変わります。送信中の項目があるとボタンが無効になり、プロンプトが重なりません。
43
+ - キュー項目の **並び替え**(ドラッグ)・**編集**・**削除**。
44
+ - 項目が `failed` になったら **再送信** をクリックします。
45
+
46
+ OpenCode インスタンスが起動していないプロジェクトは **オフライン**(グレー表示)になり、キューは保持され、再起動時にそのまま残ります。
47
+
48
+ ## Web UI の概要
49
+
50
+ | 要素 | 用途 |
51
+ |------|------|
52
+ | サイドバー | 起動中のすべてのプロジェクト(オフラインはグレー) |
53
+ | セッションタブ | プロジェクトのセッション間を切り替え |
54
+ | ステータスバッジ | 項目ごとの `queued` / `pending` / `sent` / `done` / `failed` |
55
+ | 送信 / 再送信 | キュー項目の送信、失敗項目の再試行(セッションごとに1つずつ) |
56
+
57
+ Web サーバーはプラグインのロード時に自動起動し、常にポート `4321` を使用します。
58
+
59
+ ## トラブルシューティング
60
+
61
+ - OpenCode インスタンスが1つ以上起動しているか確認してください — Web UI はプラグインが提供するため、OpenCode が起動しているときのみ `http://localhost:4321` にアクセスできます。
62
+ - ページが開かない場合、他のプログラムがすでにポート `4321` を使用していないか確認してください。
63
+ - バグを見つけましたか? 再現手順を添えて [GitHub Issue](https://github.com/dev3am/opencode-q/issues) を開いていただけると大変助かります。
64
+
65
+ ## アーキテクチャ
66
+
67
+ opencode-q は **ディスクを唯一の信頼できる情報源** として使用します。各 OpenCode インスタンスがプラグインをロードし、ポート `4321` を確保したインスタンスが(ステートレスな)Web UI を提供し、各インスタンスは自身のセッションのキュープロンプトを実行します。プロセス間のネットワークコールバックがないため、どのインスタンスが起動・終了しても他に影響しません。
68
+
69
+ 開発セットアップと詳細は [CONTRIBUTING.md](https://github.com/dev3am/opencode-q/blob/main/CONTRIBUTING.md) を参照してください。
70
+
71
+ ## ライセンス
72
+
73
+ MIT
package/README.ko.md ADDED
@@ -0,0 +1,73 @@
1
+ # opencode-q
2
+
3
+ **OpenCode용 프롬프트 큐 플러그인** — OpenCode를 실행하고 `http://localhost:4321`에 접속해, 세션별 프롬프트 큐를 할 일 목록처럼 관리합니다. 프롬프트를 미리 쌓아두고 AI에게 하나씩 전송하며, 상태를 실시간으로 추적합니다.
4
+
5
+ [English](https://github.com/dev3am/opencode-q/blob/main/README.md) | [日本語](https://github.com/dev3am/opencode-q/blob/main/README.ja.md) | [中文](https://github.com/dev3am/opencode-q/blob/main/README.zh.md)
6
+
7
+ ---
8
+
9
+ ## 무엇을 하나요
10
+
11
+ opencode-q는 **웹 전용** OpenCode 플러그인입니다. CLI도, TUI 명령어도 없습니다 — 모든 조작은 하나의 웹 UI에서 합니다.
12
+
13
+ - **모든 프로젝트를 한 웹에서** — `http://localhost:4321`에 실행 중인 모든 OpenCode 인스턴스가 한곳에 모입니다.
14
+ - **프로젝트별·세션별 큐** — 각 대화(세션)가 독립된 큐를 가집니다.
15
+ - **할 일 스타일 상태 추적** — 모든 항목은 `queued → pending → sent → done`(또는 `failed`)을 거칩니다.
16
+ - **수동·한 번에 하나** — 큐에 쌓인 프롬프트를 직접 전송하고, 이전 것이 끝나야 다음을 보냅니다. 항상 사용자가 흐름을 제어합니다.
17
+ - 큐 항목 **순서 변경·편집·삭제**, 실패 항목 **재전송**.
18
+ - **견고한 설계** — 모든 상태가 디스크에 저장되어, OpenCode 창이 열리고 닫혀도 시스템이 계속 동작합니다.
19
+
20
+ ## 설치
21
+
22
+ ### 필수 조건
23
+
24
+ - [OpenCode](https://opencode.ai) 1.x ([Bun](https://bun.sh) 위에서 동작하며 플러그인도 Bun을 사용합니다)
25
+
26
+ ### npm
27
+
28
+ ```bash
29
+ npm install -g opencode-q
30
+ ```
31
+
32
+ 설치 시 postinstall 단계에서 자동으로 `~/.config/opencode/plugins/`에 등록됩니다. OpenCode를 재시작하면 바로 사용 가능합니다.
33
+
34
+ > **참고:** 플러그인이 전역 경로(`~/.config/opencode/plugins/`)에 등록되므로 반드시 글로벌 설치(`-g`)가 필요합니다. 프로젝트별 설치는 지원하지 않습니다.
35
+
36
+ ## 사용법
37
+
38
+ 1. 아무 프로젝트에서 OpenCode를 실행합니다 (여러 프로젝트에서 동시에 실행해도 됩니다).
39
+ 2. 브라우저에서 **`http://localhost:4321`**을 엽니다.
40
+ 3. 사이드바에서 프로젝트를 고르고, 세션 탭을 선택한 뒤:
41
+ - 프롬프트 **추가** — `queued` 상태로 나타납니다.
42
+ - 큐 항목 **전송** — `pending` → AI 작업 중 `sent` → AI 완료 시 `done`으로 바뀝니다. 전송 중인 항목이 있으면 보내기 버튼이 비활성화되어 프롬프트가 겹치지 않습니다.
43
+ - 큐 항목 **순서 변경**(드래그)·**편집**·**삭제**.
44
+ - 항목이 `failed`가 되면 **재전송**을 누릅니다.
45
+
46
+ OpenCode 인스턴스가 더 이상 실행 중이지 않은 프로젝트는 **오프라인**(회색)으로 표시되며, 큐는 보존되어 재시작 시 그대로 남아 있습니다.
47
+
48
+ ## 웹 UI 한눈에 보기
49
+
50
+ | 요소 | 용도 |
51
+ |------|------|
52
+ | 사이드바 | 실행 중인 모든 프로젝트 (오프라인은 회색) |
53
+ | 세션 탭 | 프로젝트의 세션 간 전환 |
54
+ | 상태 배지 | 항목별 `queued` / `pending` / `sent` / `done` / `failed` |
55
+ | 보내기 / 재전송 | 큐 항목 전송, 실패 항목 재시도 (세션당 한 번에 하나) |
56
+
57
+ 웹 서버는 플러그인이 로드될 때 자동으로 시작되며, 항상 `4321` 포트를 사용합니다.
58
+
59
+ ## 문제 해결
60
+
61
+ - OpenCode 인스턴스가 하나 이상 실행 중인지 확인하세요 — 웹 UI는 플러그인이 제공하므로 OpenCode가 켜져 있을 때만 `http://localhost:4321`에 접속할 수 있습니다.
62
+ - 페이지가 안 열리면, 다른 프로그램이 `4321` 포트를 이미 쓰고 있지 않은지 확인하세요.
63
+ - 버그를 발견하셨나요? 재현 단계와 함께 [GitHub Issue](https://github.com/dev3am/opencode-q/issues)를 열어주시면 큰 도움이 됩니다.
64
+
65
+ ## 아키텍처
66
+
67
+ opencode-q는 **디스크를 유일한 진실원**으로 사용합니다. 각 OpenCode 인스턴스가 플러그인을 로드하고, `4321` 포트를 잡은 인스턴스가 (무상태) 웹 UI를 제공하며, 각 인스턴스는 자기 세션의 큐 프롬프트를 실행합니다. 프로세스 간 네트워크 콜백이 없어, 어떤 인스턴스가 켜지고 꺼지든 나머지에 영향을 주지 않습니다.
68
+
69
+ 개발 설정 및 상세 내용은 [CONTRIBUTING.md](https://github.com/dev3am/opencode-q/blob/main/CONTRIBUTING.md)를 참조하세요.
70
+
71
+ ## 라이선스
72
+
73
+ MIT
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # opencode-q
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/dev3am/opencode-q/main/assets/readme_hero_pixel.png" alt="opencode-q" width="600" />
5
+ </p>
6
+
7
+ **Prompt queue plugin for OpenCode** — run OpenCode, open `http://localhost:4321`, and manage a per-session prompt queue like a todo list: stage prompts ahead of time and send them to the AI one at a time, with live status tracking.
8
+
9
+ [한국어](https://github.com/dev3am/opencode-q/blob/main/README.ko.md) | [日本語](https://github.com/dev3am/opencode-q/blob/main/README.ja.md) | [中文](https://github.com/dev3am/opencode-q/blob/main/README.zh.md)
10
+
11
+ ---
12
+
13
+ ## What it does
14
+
15
+ opencode-q is a **web-only** OpenCode plugin. There is no CLI and there are no TUI commands — you drive everything from one web UI.
16
+
17
+ - **One web UI for every project** at `http://localhost:4321` — all running OpenCode instances show up in one place.
18
+ - **Per-project, per-session queues** — each conversation (session) has its own independent queue.
19
+ - **Todo-style status tracking** — every item moves through `queued → pending → sent → done` (or `failed`).
20
+ - **Manual, one at a time** — you send a queued prompt explicitly; the next one is sent only after the previous finishes. You stay in control.
21
+ - **Reorder, edit, delete** queued items; **resend** failed ones.
22
+ - **Robust by design** — all state lives on disk, so the system keeps working even as OpenCode windows open and close.
23
+
24
+ ## Installation
25
+
26
+ ### Prerequisites
27
+
28
+ - [OpenCode](https://opencode.ai) 1.x (it runs on [Bun](https://bun.sh), which the plugin uses too)
29
+
30
+ ### npm
31
+
32
+ ```bash
33
+ npm install -g opencode-q
34
+ ```
35
+
36
+ The plugin auto-installs to `~/.config/opencode/plugins/` via a postinstall step. Restart OpenCode and it works.
37
+
38
+ > **Note:** Global install (`-g`) is required because the plugin registers globally at `~/.config/opencode/plugins/`. Per-project install is not supported.
39
+
40
+ ## Usage
41
+
42
+ 1. Start OpenCode in any project (start it in several projects if you like).
43
+ 2. Open **`http://localhost:4321`** in your browser.
44
+ 3. In the sidebar, pick a project; pick a session tab; then:
45
+ - **Add** a prompt — it appears as `queued`.
46
+ - **Send** a queued prompt — it goes to `pending`, then `sent` while the AI works, then `done` when the AI finishes. Send is disabled while an item is in flight, so prompts never overlap.
47
+ - **Reorder** (drag), **edit**, or **delete** queued items.
48
+ - If an item ends up `failed`, click **resend**.
49
+
50
+ Projects whose OpenCode instance is no longer running are shown as **offline** (greyed out); their queues are preserved for when you restart.
51
+
52
+ ## Web UI at a glance
53
+
54
+ | Element | Purpose |
55
+ |---------|---------|
56
+ | Sidebar | All running projects (offline ones greyed) |
57
+ | Session tabs | Switch between a project's sessions |
58
+ | Status badge | `queued` / `pending` / `sent` / `done` / `failed` per item |
59
+ | Send / Resend | Dispatch a queued item, or retry a failed one (one at a time per session) |
60
+
61
+ The web server starts automatically when the plugin loads. It always listens on port `4321`.
62
+
63
+ ## Troubleshooting
64
+
65
+ - Make sure at least one OpenCode instance is running — the web UI is served by the plugin, so `http://localhost:4321` is only available while OpenCode is open.
66
+ - If the page does not load, confirm nothing else on your machine is already using port `4321`.
67
+ - Found a bug? Please open a [GitHub Issue](https://github.com/dev3am/opencode-q/issues) with steps to reproduce — it helps a lot.
68
+
69
+ ## Architecture
70
+
71
+ opencode-q uses **disk as the single source of truth**. Each OpenCode instance loads the plugin; whichever instance grabs port `4321` serves the (stateless) web UI, and every instance executes the queued prompts for its own sessions. There are no cross-process network callbacks, so any instance can come and go without breaking the others.
72
+
73
+ For development setup and details, see [CONTRIBUTING.md](https://github.com/dev3am/opencode-q/blob/main/CONTRIBUTING.md).
74
+
75
+ ## License
76
+
77
+ MIT
package/README.zh.md ADDED
@@ -0,0 +1,73 @@
1
+ # opencode-q
2
+
3
+ **OpenCode 的提示队列插件** — 运行 OpenCode,打开 `http://localhost:4321`,像管理待办清单一样管理每个会话的提示队列:提前暂存提示,逐个发送给 AI,并实时追踪状态。
4
+
5
+ [English](https://github.com/dev3am/opencode-q/blob/main/README.md) | [한국어](https://github.com/dev3am/opencode-q/blob/main/README.ko.md) | [日本語](https://github.com/dev3am/opencode-q/blob/main/README.ja.md)
6
+
7
+ ---
8
+
9
+ ## 它能做什么
10
+
11
+ opencode-q 是一个 **纯 Web** 的 OpenCode 插件。没有 CLI,也没有 TUI 命令 — 所有操作都在一个 Web UI 中完成。
12
+
13
+ - **一个 Web 管理所有项目** — `http://localhost:4321` 汇集所有正在运行的 OpenCode 实例。
14
+ - **按项目、按会话的队列** — 每个对话(会话)拥有独立的队列。
15
+ - **待办式状态追踪** — 每个项目经历 `queued → pending → sent → done`(或 `failed`)。
16
+ - **手动、逐个发送** — 显式发送队列中的提示,上一个完成后才发送下一个。始终由用户掌控流程。
17
+ - 队列项目的 **重新排序、编辑、删除**,失败项目的 **重新发送**。
18
+ - **稳健的设计** — 所有状态保存在磁盘上,因此即使 OpenCode 窗口开关,系统也能持续工作。
19
+
20
+ ## 安装
21
+
22
+ ### 前提条件
23
+
24
+ - [OpenCode](https://opencode.ai) 1.x(运行于 [Bun](https://bun.sh) 之上,插件也使用 Bun)
25
+
26
+ ### npm
27
+
28
+ ```bash
29
+ npm install -g opencode-q
30
+ ```
31
+
32
+ 安装时通过 postinstall 步骤自动注册到 `~/.config/opencode/plugins/`。重启 OpenCode 即可使用。
33
+
34
+ > **注意:** 由于插件注册到全局路径(`~/.config/opencode/plugins/`),因此必须使用全局安装(`-g`)。不支持按项目安装。
35
+
36
+ ## 使用方法
37
+
38
+ 1. 在任意项目中启动 OpenCode(也可以在多个项目中同时启动)。
39
+ 2. 在浏览器中打开 **`http://localhost:4321`**。
40
+ 3. 在侧边栏选择项目,选择会话标签,然后:
41
+ - **添加** 提示 — 显示为 `queued`。
42
+ - **发送** 队列项目 — 变为 `pending` → AI 工作时为 `sent` → AI 完成时为 `done`。有项目正在发送时按钮会被禁用,避免提示重叠。
43
+ - 队列项目的 **重新排序**(拖拽)、**编辑**、**删除**。
44
+ - 项目变为 `failed` 时,点击 **重新发送**。
45
+
46
+ OpenCode 实例已不再运行的项目会显示为 **离线**(灰色),其队列会被保留,重启时仍然存在。
47
+
48
+ ## Web UI 一览
49
+
50
+ | 元素 | 用途 |
51
+ |------|------|
52
+ | 侧边栏 | 所有正在运行的项目(离线为灰色) |
53
+ | 会话标签 | 在项目的会话间切换 |
54
+ | 状态徽章 | 每个项目的 `queued` / `pending` / `sent` / `done` / `failed` |
55
+ | 发送 / 重新发送 | 发送队列项目,重试失败项目(每个会话一次一个) |
56
+
57
+ Web 服务器在插件加载时自动启动,始终使用端口 `4321`。
58
+
59
+ ## 问题排查
60
+
61
+ - 确认至少有一个 OpenCode 实例正在运行 — Web UI 由插件提供,因此只有在 OpenCode 打开时才能访问 `http://localhost:4321`。
62
+ - 如果页面无法打开,请确认没有其他程序已占用端口 `4321`。
63
+ - 发现 Bug?请附上复现步骤开一个 [GitHub Issue](https://github.com/dev3am/opencode-q/issues),这会很有帮助。
64
+
65
+ ## 架构
66
+
67
+ opencode-q 以 **磁盘作为唯一可信来源**。每个 OpenCode 实例加载插件,抢到端口 `4321` 的实例提供(无状态的)Web UI,每个实例执行自身会话的队列提示。进程间没有网络回调,因此任何实例的启动或关闭都不会影响其他实例。
68
+
69
+ 开发设置和详细信息请参阅 [CONTRIBUTING.md](https://github.com/dev3am/opencode-q/blob/main/CONTRIBUTING.md)。
70
+
71
+ ## 许可证
72
+
73
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __require = import.meta.require;
20
+
21
+ // cli/index.ts
22
+ import * as http from "http";
23
+
24
+ // src/constants.ts
25
+ var PREVIEW_LENGTH = 50;
26
+ var DEFAULT_PORT = 4321;
27
+
28
+ // cli/index.ts
29
+ var args = process.argv.slice(2);
30
+ function parseFlag(args2, flag, shortFlag) {
31
+ const rest = [];
32
+ let value = null;
33
+ for (let i = 0;i < args2.length; i++) {
34
+ if (args2[i] === flag || args2[i] === shortFlag) {
35
+ value = args2[i + 1] || null;
36
+ i++;
37
+ } else {
38
+ rest.push(args2[i]);
39
+ }
40
+ }
41
+ return { value, rest };
42
+ }
43
+ var { value: projectPath, rest: rest1 } = parseFlag(args, "--project", "-p");
44
+ var { value: rawSession, rest: positional } = parseFlag(rest1, "--session", "-s");
45
+ var baseDir = projectPath || process.cwd();
46
+ var sessionId = rawSession || "default";
47
+ var serverPort = parseInt(process.env.OPENCODE_Q_PORT || String(DEFAULT_PORT), 10);
48
+ function request2(method, path, body) {
49
+ return new Promise((resolve, reject) => {
50
+ const bodyStr = body !== undefined ? JSON.stringify(body) : undefined;
51
+ const opts = {
52
+ hostname: "127.0.0.1",
53
+ port: serverPort,
54
+ path,
55
+ method,
56
+ headers: { "Content-Type": "application/json" }
57
+ };
58
+ if (bodyStr)
59
+ opts.headers["Content-Length"] = Buffer.byteLength(bodyStr);
60
+ const req = http.request(opts, (res) => {
61
+ const chunks = [];
62
+ res.on("data", (chunk) => chunks.push(chunk));
63
+ res.on("end", () => {
64
+ const text = Buffer.concat(chunks).toString();
65
+ try {
66
+ resolve({ status: res.statusCode || 200, json: JSON.parse(text) });
67
+ } catch {
68
+ resolve({ status: res.statusCode || 200, json: text });
69
+ }
70
+ });
71
+ });
72
+ req.on("error", reject);
73
+ if (bodyStr)
74
+ req.write(bodyStr);
75
+ req.end();
76
+ });
77
+ }
78
+ function projectBase() {
79
+ return `/api/projects/${encodeURIComponent(baseDir)}`;
80
+ }
81
+ function queueBase() {
82
+ return `${projectBase()}/queue/${sessionId}`;
83
+ }
84
+ function showHelp() {
85
+ console.log(`opencode-q \u2014 Prompt queue manager for OpenCode
86
+
87
+ Usage:
88
+ opencode-q add <text> [-p <path>] [-s <id>] Add prompt
89
+ opencode-q list [-p <path>] [-s <id>] List queue
90
+ opencode-q remove <id> [-p <path>] [-s <id>] Remove by ID
91
+ opencode-q clear [-p <path>] [-s <id>] Clear queue
92
+ opencode-q reorder <from> <to> [-p <path>] [-s <id>] Reorder (1-based)
93
+ opencode-q peek [-p <path>] [-s <id>] Show next prompt
94
+ opencode-q next [-p <path>] [-s <id>] Execute next (dequeue)
95
+ opencode-q sessions [-p <path>] List sessions
96
+ opencode-q projects List active projects
97
+ opencode-q logs Show error logs
98
+ opencode-q help Show this help
99
+
100
+ Options:
101
+ --project, -p <path> Target project directory (default: cwd)
102
+ --session, -s <id> Target session ID (default: "default")
103
+
104
+ Environment:
105
+ OPENCODE_Q_PORT Server port (default: 4321)`);
106
+ }
107
+ async function requireServer() {
108
+ try {
109
+ await request2("GET", "/api/projects");
110
+ return true;
111
+ } catch {
112
+ console.error(`Error: opencode-q server is not running on port ${serverPort}`);
113
+ console.error(`Start OpenCode first, or the server will auto-start when a plugin loads.`);
114
+ return false;
115
+ }
116
+ }
117
+ var command = positional[0];
118
+ async function main() {
119
+ switch (command) {
120
+ case "projects": {
121
+ if (!await requireServer())
122
+ return;
123
+ const res = await request2("GET", "/api/projects");
124
+ if (res.json.projects.length === 0) {
125
+ console.log("No active projects");
126
+ return;
127
+ }
128
+ for (const p of res.json.projects) {
129
+ const name = p.baseDir.split("/").pop() || p.baseDir;
130
+ const sessions = p.sessions.map((s) => `${s.sessionId} (${s.status})`).join(", ");
131
+ console.log(` ${name} \u2014 ${p.baseDir} [${sessions}]`);
132
+ }
133
+ return;
134
+ }
135
+ case "logs": {
136
+ const { homedir } = await import("os");
137
+ const { join } = await import("path");
138
+ const { existsSync, readFileSync } = await import("fs");
139
+ const logFile = join(homedir(), ".config", "opencode", "opencode-q-errors.log");
140
+ if (!existsSync(logFile)) {
141
+ console.log("No error logs found.");
142
+ return;
143
+ }
144
+ const raw = readFileSync(logFile, "utf-8").trim();
145
+ if (!raw) {
146
+ console.log("No error logs found.");
147
+ return;
148
+ }
149
+ const logs = raw.split(`
150
+ `);
151
+ console.log(`--- Error Logs (${logs.length} entries) ---`);
152
+ for (const line of logs) {
153
+ if (!line)
154
+ continue;
155
+ try {
156
+ const entry = JSON.parse(line);
157
+ console.log(`[${entry.timestamp}] Project: ${entry.project || "none"}`);
158
+ console.log(`Message: ${entry.message}`);
159
+ if (entry.stack) {
160
+ console.log(`Stack:
161
+ ${entry.stack.split(`
162
+ `).slice(0, 5).join(`
163
+ `)}`);
164
+ }
165
+ console.log("-".repeat(40));
166
+ } catch {
167
+ console.log(line);
168
+ }
169
+ }
170
+ return;
171
+ }
172
+ case "add": {
173
+ if (!await requireServer())
174
+ return;
175
+ const text = positional.slice(1).join(" ");
176
+ if (!text) {
177
+ console.error("Error: prompt text required");
178
+ process.exit(1);
179
+ }
180
+ const res = await request2("POST", queueBase(), { text });
181
+ if (res.status !== 201) {
182
+ console.error(`Error: ${res.json.error}`);
183
+ process.exit(1);
184
+ }
185
+ console.log(`Added #${res.json.item.id} [project: ${baseDir.split("/").pop()}, session: ${sessionId}]`);
186
+ return;
187
+ }
188
+ case "list": {
189
+ if (!await requireServer())
190
+ return;
191
+ const res = await request2("GET", queueBase());
192
+ if (res.json.items.length === 0) {
193
+ console.log(`Queue is empty [session: ${sessionId}]`);
194
+ return;
195
+ }
196
+ console.log(`Project: ${baseDir.split("/").pop()} | Session: ${sessionId}`);
197
+ for (let i = 0;i < res.json.items.length; i++) {
198
+ const item = res.json.items[i];
199
+ const preview = item.text.length > PREVIEW_LENGTH ? item.text.slice(0, PREVIEW_LENGTH) + "..." : item.text;
200
+ console.log(`${i + 1}. [${item.id}] ${preview}`);
201
+ }
202
+ return;
203
+ }
204
+ case "remove": {
205
+ if (!await requireServer())
206
+ return;
207
+ const id = positional[1];
208
+ if (!id) {
209
+ console.error("Error: ID required");
210
+ process.exit(1);
211
+ }
212
+ const res = await request2("DELETE", `${queueBase()}/${id}`);
213
+ if (res.status !== 200) {
214
+ console.error(`Not found (${id})`);
215
+ process.exit(1);
216
+ }
217
+ console.log(`Removed (${id}) [session: ${sessionId}]`);
218
+ return;
219
+ }
220
+ case "clear": {
221
+ if (!await requireServer())
222
+ return;
223
+ await request2("DELETE", queueBase());
224
+ console.log(`Queue cleared [session: ${sessionId}]`);
225
+ return;
226
+ }
227
+ case "reorder": {
228
+ if (!await requireServer())
229
+ return;
230
+ const from = parseInt(positional[1], 10);
231
+ const to = parseInt(positional[2], 10);
232
+ if (isNaN(from) || isNaN(to)) {
233
+ console.error("Error: from and to positions required (numbers)");
234
+ process.exit(1);
235
+ }
236
+ const res = await request2("PATCH", `${queueBase()}/reorder`, { from, to });
237
+ if (res.status !== 200) {
238
+ console.error(`Error: ${res.json.error}`);
239
+ process.exit(1);
240
+ }
241
+ console.log(`Reordered [session: ${sessionId}]:`);
242
+ for (let i = 0;i < res.json.items.length; i++)
243
+ console.log(` ${i + 1}. [${res.json.items[i].id}]`);
244
+ return;
245
+ }
246
+ case "peek": {
247
+ if (!await requireServer())
248
+ return;
249
+ const listRes = await request2("GET", queueBase());
250
+ if (listRes.json.items.length === 0) {
251
+ console.log(`Queue is empty [session: ${sessionId}]`);
252
+ return;
253
+ }
254
+ const item = listRes.json.items[0];
255
+ console.log(`[${item.id}] ${item.text} [session: ${sessionId}]`);
256
+ return;
257
+ }
258
+ case "next": {
259
+ if (!await requireServer())
260
+ return;
261
+ const res = await request2("POST", `${queueBase()}/next`);
262
+ if (!res.json.executed) {
263
+ console.log(`Queue is empty [session: ${sessionId}]`);
264
+ return;
265
+ }
266
+ console.log(res.json.item.text);
267
+ return;
268
+ }
269
+ case "sessions": {
270
+ if (!await requireServer())
271
+ return;
272
+ const projRes = await request2("GET", projectBase());
273
+ const data = projRes.json;
274
+ if (data.sessions) {
275
+ for (const s of data.sessions)
276
+ console.log(` ${s.sessionId} (${s.status})`);
277
+ } else {
278
+ console.log("No sessions found");
279
+ }
280
+ return;
281
+ }
282
+ case "help":
283
+ case "--help":
284
+ case "-h":
285
+ showHelp();
286
+ return;
287
+ default:
288
+ console.error(`Unknown command: ${command || "(none)"}`);
289
+ showHelp();
290
+ process.exit(1);
291
+ }
292
+ }
293
+ main().catch((e) => {
294
+ console.error(`Fatal: ${e.message}`);
295
+ process.exit(1);
296
+ });