opencode-codeops 2.2.0 → 2.2.1
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to CodeOps are recorded here.
|
|
4
4
|
|
|
5
|
+
## 2.2.1 — 2026-10-10
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
- scripts: pin the changelog feature guard to the document, not one release
|
|
10
|
+
- tui: precompile the packaged TUI entry for npm installs
|
|
11
|
+
|
|
5
12
|
## 2.2.0 — 2026-10-10
|
|
6
13
|
|
|
7
14
|
### Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codeops",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Specification-first engineering for complex systems — CodeOps plugin for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
"exports": {
|
|
12
12
|
".": "./plugin/index.ts",
|
|
13
13
|
"./server": "./plugin/index.ts",
|
|
14
|
-
"./tui": "./plugin/tui.
|
|
14
|
+
"./tui": "./plugin/tui.js"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"opencode-codeops": "bin/index.mjs"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node --test",
|
|
21
|
+
"build:tui": "node scripts/build-tui.mjs",
|
|
21
22
|
"typecheck": "tsc --noEmit",
|
|
22
23
|
"check:version": "node scripts/check-version.mjs",
|
|
23
24
|
"verify": "npm run typecheck && npm run test && npm run check:version",
|
package/plugin/tui.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Generated from plugin/tui.tsx — do not edit; run `npm run build:tui` to regenerate.
|
|
2
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
3
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
4
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
5
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
6
|
+
/**
|
|
7
|
+
* The CodeOps TUI entry: the live run-progress view in the session sidebar.
|
|
8
|
+
*
|
|
9
|
+
* The view is deliberately honest: it shows only reported values from the
|
|
10
|
+
* server's run state, always with an as-of time, and it renders nothing when
|
|
11
|
+
* there is no run, when the host lacks the events API, or when every call
|
|
12
|
+
* fails. There are no timers — the text recomputes whenever the component
|
|
13
|
+
* re-renders — and no file access; all display and merge logic lives in the
|
|
14
|
+
* shared helper module so it can be tested under plain Node.
|
|
15
|
+
*
|
|
16
|
+
* @module tui
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Plugin, usePlugin } from "@opencode/plugin/tui";
|
|
20
|
+
import { createEffect, createResource, createSignal, onCleanup, Show } from "solid-js";
|
|
21
|
+
import { acceptRunCleared, acceptRunUpdate, describeRun, mergeRunState } from "../bin/lib/codeops-progress.mjs";
|
|
22
|
+
import { CodeOpsRpc, requestProgress } from "../bin/lib/codeops-rpc.mjs";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The live run-progress view.
|
|
26
|
+
*
|
|
27
|
+
* Resolves the session's location once, subscribes to the `updated` and
|
|
28
|
+
* `cleared` events before requesting an initial snapshot, and merges both
|
|
29
|
+
* delivery paths monotonically. A clear records its server-stamped
|
|
30
|
+
* `clearedAt`; both merge paths ignore deliveries at or before it, so a late
|
|
31
|
+
* snapshot or update can never resurrect a cleared run. Every failure path is
|
|
32
|
+
* silent: a host without the events API still renders from the snapshot, and
|
|
33
|
+
* an invalid or missing snapshot renders nothing.
|
|
34
|
+
*
|
|
35
|
+
* @returns The three display lines, or nothing when no run is known.
|
|
36
|
+
*/
|
|
37
|
+
function CodeOpsProgress() {
|
|
38
|
+
const context = usePlugin();
|
|
39
|
+
const location = context.location ?? context.data.location.default();
|
|
40
|
+
const directory = location?.directory ?? "";
|
|
41
|
+
const [run, setRun] = createSignal(null);
|
|
42
|
+
const [clearedAt, setClearedAt] = createSignal(0);
|
|
43
|
+
|
|
44
|
+
// Subscribe BEFORE the snapshot: events are live-only, so a snapshot
|
|
45
|
+
// fetched first could miss an update arriving in between. Guarded: a host
|
|
46
|
+
// without the events API still renders from the snapshot below.
|
|
47
|
+
const stops = [];
|
|
48
|
+
try {
|
|
49
|
+
const events = context.client.rpc(CodeOpsRpc).events;
|
|
50
|
+
const stopUpdated = events.on("updated", event => {
|
|
51
|
+
const snapshot = acceptRunUpdate(event, directory);
|
|
52
|
+
if (snapshot && snapshot.updatedAt > clearedAt()) {
|
|
53
|
+
setRun(current => mergeRunState(current, snapshot));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
stops.push(stopUpdated);
|
|
57
|
+
const stopCleared = events.on("cleared", event => {
|
|
58
|
+
const cleared = acceptRunCleared(event, directory);
|
|
59
|
+
if (!cleared) return;
|
|
60
|
+
// Record the clear time for every accepted clear, before the identity
|
|
61
|
+
// gate: a clear that arrives before the initial snapshot must still
|
|
62
|
+
// suppress that snapshot, or a cleared run could reappear.
|
|
63
|
+
setClearedAt(cleared.clearedAt);
|
|
64
|
+
const current = run();
|
|
65
|
+
if (current && cleared.plan === current.plan && cleared.sessionID === current.sessionID) {
|
|
66
|
+
setRun(null);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
stops.push(stopCleared);
|
|
70
|
+
} catch {
|
|
71
|
+
// Events unavailable: the snapshot-only view below still renders.
|
|
72
|
+
}
|
|
73
|
+
onCleanup(() => stops.forEach(stop => stop()));
|
|
74
|
+
|
|
75
|
+
// Initial snapshot; null or a failure means "nothing known". The clear
|
|
76
|
+
// guard applies here too: a snapshot at or before the last clear is ignored.
|
|
77
|
+
const [initial] = createResource(async () => requestProgress(context.client, {
|
|
78
|
+
location
|
|
79
|
+
}));
|
|
80
|
+
createEffect(() => {
|
|
81
|
+
const snapshot = initial();
|
|
82
|
+
if (snapshot && snapshot.updatedAt > clearedAt()) {
|
|
83
|
+
setRun(current => mergeRunState(current, snapshot));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return _$createComponent(Show, {
|
|
87
|
+
get when() {
|
|
88
|
+
return run();
|
|
89
|
+
},
|
|
90
|
+
children: snapshot => {
|
|
91
|
+
const view = () => describeRun(snapshot(), Date.now());
|
|
92
|
+
return _$memo(() => view().lines.map(line => (() => {
|
|
93
|
+
var _el$ = _$createElement("text");
|
|
94
|
+
_$insert(_el$, line);
|
|
95
|
+
return _el$;
|
|
96
|
+
})()));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The opencode-codeops TUI plugin: claims the sidebar content slot with the
|
|
103
|
+
* live run view. The host disposes the claim when the plugin unloads.
|
|
104
|
+
*/
|
|
105
|
+
export default Plugin.define({
|
|
106
|
+
id: "opencode-codeops-tui",
|
|
107
|
+
setup(context) {
|
|
108
|
+
return context.ui.slot({
|
|
109
|
+
append: "sidebar.content",
|
|
110
|
+
render: () => _$createComponent(CodeOpsProgress, {})
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the packaged TUI entry.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode transpiles plugin TSX with its OpenTUI renderer runtime only for
|
|
5
|
+
* files outside `node_modules`. A published package always installs into
|
|
6
|
+
* `node_modules`, so a source `.tsx` entry falls back to the default React
|
|
7
|
+
* JSX transform and fails to load ("Cannot find package 'react'"). Compiling
|
|
8
|
+
* `plugin/tui.tsx` with `@opentui/solid`'s own transform — the same pipeline
|
|
9
|
+
* the host applies to project-local plugins — produces a plain-JavaScript
|
|
10
|
+
* entry whose runtime imports (`@opentui/solid`, `solid-js`) the host
|
|
11
|
+
* rewrites to its bundled renderer.
|
|
12
|
+
*
|
|
13
|
+
* The generated artifact `plugin/tui.js` is committed and shipped as the
|
|
14
|
+
* `./tui` export; `scripts/tui-entry-build.spec.test.mjs` fails when it is
|
|
15
|
+
* stale, so the release verification blocks a stale entry.
|
|
16
|
+
*
|
|
17
|
+
* @module build-tui
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync, writeFileSync } from "node:fs"
|
|
21
|
+
import { createRequire } from "node:module"
|
|
22
|
+
import { dirname, join } from "node:path"
|
|
23
|
+
import { fileURLToPath, pathToFileURL } from "node:url"
|
|
24
|
+
|
|
25
|
+
/** Repository root: this file lives in `<root>/scripts/`. */
|
|
26
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
27
|
+
|
|
28
|
+
/** Source of the TUI entry, authored as TSX. */
|
|
29
|
+
const SOURCE = join(ROOT, "plugin", "tui.tsx")
|
|
30
|
+
|
|
31
|
+
/** Generated JavaScript entry shipped as `./tui`. */
|
|
32
|
+
const TARGET = join(ROOT, "plugin", "tui.js")
|
|
33
|
+
|
|
34
|
+
/** Header marking the artifact as generated. */
|
|
35
|
+
const HEADER =
|
|
36
|
+
"// Generated from plugin/tui.tsx — do not edit; run `npm run build:tui` to regenerate.\n"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load `@opentui/solid`'s Solid JSX transform.
|
|
40
|
+
*
|
|
41
|
+
* The transform module is not part of the package's public exports map, so it
|
|
42
|
+
* is resolved through the package root: resolve the package entry point, then
|
|
43
|
+
* import the transform beside it.
|
|
44
|
+
*
|
|
45
|
+
* @returns The `transformSolidSource(code, options)` function.
|
|
46
|
+
*/
|
|
47
|
+
async function loadTransform() {
|
|
48
|
+
const require = createRequire(join(ROOT, "scripts", "build-tui.mjs"))
|
|
49
|
+
const packageEntry = require.resolve("@opentui/solid")
|
|
50
|
+
const modulePath = join(dirname(packageEntry), "scripts", "solid-transform.js")
|
|
51
|
+
const module = await import(pathToFileURL(modulePath).href)
|
|
52
|
+
return module.transformSolidSource
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Compile the current `plugin/tui.tsx` into the shipped entry source.
|
|
57
|
+
*
|
|
58
|
+
* @returns The complete `plugin/tui.js` content, header included.
|
|
59
|
+
*/
|
|
60
|
+
export async function buildTuiSource() {
|
|
61
|
+
const transformSolidSource = await loadTransform()
|
|
62
|
+
const source = readFileSync(SOURCE, "utf8")
|
|
63
|
+
const transformed = await transformSolidSource(source, { filename: SOURCE })
|
|
64
|
+
const code = typeof transformed === "string" ? transformed : transformed.code
|
|
65
|
+
return HEADER + code
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// When run directly (`npm run build:tui`), regenerate the committed artifact.
|
|
69
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
70
|
+
const code = await buildTuiSource()
|
|
71
|
+
writeFileSync(TARGET, code)
|
|
72
|
+
console.log(`built plugin/tui.js (${code.length} chars)`)
|
|
73
|
+
}
|