@litfamily/litopencode 1.0.36 โ 1.0.38
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 +16 -0
- package/README-Ko-KR.md +6 -6
- package/README.md +6 -6
- package/dist/state.d.ts +1 -0
- package/dist/state.js +1 -0
- package/dist/tools.d.ts +15 -5
- package/dist/tui/mission-band.d.ts +7 -0
- package/dist/tui/mission-band.js +4 -0
- package/dist/tui/mission-skills.js +3 -1
- package/dist/tui/mission-source-v2.d.ts +23 -0
- package/dist/tui/mission-source-v2.js +29 -4
- package/dist/tui/mission-source.d.ts +1 -0
- package/dist/tui/mission-source.js +3 -2
- package/dist/tui/mission-text.d.ts +3 -3
- package/dist/tui/mission-text.js +5 -5
- package/dist/v2/events.d.ts +1 -1
- package/dist/v2/events.js +31 -5
- package/dist/v2/notices.d.ts +13 -0
- package/dist/v2/notices.js +143 -0
- package/dist/v2/tui-footer.d.ts +20 -0
- package/dist/v2/tui-footer.js +56 -0
- package/dist/v2/tui-notices.d.ts +7 -0
- package/dist/v2/tui-notices.js +45 -0
- package/dist/v2/tui.d.ts +43 -1
- package/dist/v2/tui.js +345 -32
- package/dist/v2/usage-limits.d.ts +19 -0
- package/dist/v2/usage-limits.js +70 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/reference-Ko-KR.md +24 -5
- package/docs/reference.md +29 -7
- package/package.json +6 -2
- package/skills/lit-typographic-motion/engine/cover/render.mjs +1 -1
- package/skills/lit-typographic-motion/engine/stage/capture.mjs +3 -2
- package/skills/lit-typographic-motion/engine/stage/gate.mjs +1 -0
- package/skills/lit-typographic-motion/engine/stage/render.mjs +1 -0
- package/skills/lit-typographic-motion/engine/stage/scan.mjs +109 -5
- package/skills/lit-typographic-motion/engine/stage/stage-kit.js +39 -1
- package/skills/lit-typographic-motion/references/cover.md +2 -1
- package/skills/lit-typographic-motion/references/runtime.md +1 -1
- package/skills/lit-typographic-motion/references/stage.md +48 -2
- package/skills/lit-typographic-motion/requirements-audio.txt +92 -89
- package/skills/managed-skill-manifest.json +13 -13
- package/skills/readme-studio/templates/hyperframes/package-lock.json +4 -4
- package/skills/readme-studio/templates/remotion/package-lock.json +3 -3
- package/skills/readme-studio/templates/remotion/package.json +1 -1
package/dist/v2/tui.js
CHANGED
|
@@ -1,26 +1,57 @@
|
|
|
1
|
-
// LitOpenCode's OpenCode 2 TUI plugin: the
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// LitOpenCode's OpenCode 2 TUI plugin: the same HUD OpenCode 1 draws, placed in the slots OpenCode 2
|
|
2
|
+
// publishes (@opencode/plugin SlotMap). The status card sits after the prompt footer, the stage chip and
|
|
3
|
+
// the ignition line (`๐ฅ LIT IGNITED ยท <discipline>`, the OpenCode 2 home of the OpenCode 1 completion
|
|
4
|
+
// mark) share prompt.footer.status, the home line is in home.footer and the mission panel in
|
|
5
|
+
// sidebar.content. Toasts cover the gate events, the stage moving, discipline switches, lanes, the
|
|
6
|
+
// ignition and the restart notice, with an opt-in finish alert. OpenCode 2 loads the plugin on its own
|
|
7
|
+
// for an active server plugin. The server cannot raise toasts there, so the ignition toast is inferred
|
|
8
|
+
// from the lit mark the server writes into the user message metadata. It only reads state. OpenTUI and
|
|
9
|
+
// Solid come from the host at run time; tests pass a stand-in runtime.
|
|
7
10
|
import { activationDiscipline } from "../activation-probe.js";
|
|
11
|
+
import { readPackageMetadata } from "../cli/json.js";
|
|
8
12
|
import { micro, supportsMarkGlyphs } from "../lit-mark.js";
|
|
9
|
-
import {
|
|
13
|
+
import { readInstalledVersions, restartVersion } from "../restart-notice.js";
|
|
14
|
+
import { buildMissionView, lastMission, leadingNotice, missionNotices } from "../tui/mission-model.js";
|
|
10
15
|
import { idleMotion, motionTiming, planMotion, sampleMotion } from "../tui/mission-motion.js";
|
|
11
|
-
import { latestLitMarkV2, missionStateFromV2 } from "../tui/mission-source-v2.js";
|
|
12
|
-
import { createLedgerReader, createWorkReader, missionProjectRoot, readLatestResearchRun, readMissionInput } from "../tui/mission-source.js";
|
|
13
|
-
import {
|
|
16
|
+
import { latestLitMarkV2, missionStateFromV2, sessionMessagesV2 } from "../tui/mission-source-v2.js";
|
|
17
|
+
import { adhdSuggestDue, createLedgerReader, createWorkReader, earlierWorkWindowMs, hasEarlierWork, messageTimes, missionProjectRoot, readCardInput, readLatestResearchRun, readMissionInput, readThreadSources, wayBackDue } from "../tui/mission-source.js";
|
|
18
|
+
import { chipShimmer, idleChipMotion, planChipMotion } from "../tui/mission-skills.js";
|
|
19
|
+
import { bandCard, readBandOptions, readNotifyOptions, statusCard } from "../tui/mission-band.js";
|
|
20
|
+
import { activeSkillLook, turnGlow, waitingOnYou } from "../tui/mission-looks.js";
|
|
21
|
+
import { rainbowActive, rainbowAllowed, rainbowAt, rainbowShows, rainbowTickMs } from "../tui/mission-rainbow.js";
|
|
22
|
+
import { threadLineCount, threadsOf } from "../tui/mission-threads.js";
|
|
23
|
+
import { homeLine, missionPanel, paletteFromTheme, panelWidth } from "../tui/mission-text.js";
|
|
24
|
+
import { createRuntimePaths } from "../state.js";
|
|
25
|
+
import { footerBudget, footerStatus, sidebarCells } from "./tui-footer.js";
|
|
26
|
+
import { createNoticeTail } from "./notices.js";
|
|
27
|
+
import { v2StageNotices } from "./tui-notices.js";
|
|
28
|
+
import { createUsageLimits } from "./usage-limits.js";
|
|
14
29
|
const refreshDelayMs = 80;
|
|
15
30
|
const ledgerPollMs = 2000;
|
|
16
31
|
const toastMs = 4000;
|
|
17
32
|
const ignitionToastMs = 6000;
|
|
33
|
+
const liveTickMs = 1000;
|
|
34
|
+
const restartCheckMs = 10 * 60_000;
|
|
18
35
|
const trackedSessionLimit = 4;
|
|
36
|
+
const homeLineWidth = 69;
|
|
19
37
|
// A lit mark older than the moment a session is first drawn is history being loaded, not a new turn.
|
|
20
38
|
const historySlackMs = 2000;
|
|
39
|
+
const defaultWidth = 120;
|
|
40
|
+
const sidebarMinWidth = 120;
|
|
41
|
+
// The plan windows change slowly; asking the host more than once a minute would only add requests.
|
|
42
|
+
const limitsPollMs = 60_000;
|
|
43
|
+
// Only the OpenCode Go allowance has a source on OpenCode 2, so the windows belong to a Go session alone.
|
|
44
|
+
const goProviderID = "opencode-go";
|
|
45
|
+
const noticeBurst = 3;
|
|
21
46
|
async function hostRuntime() {
|
|
22
|
-
const [jsxRuntime, solid] = await Promise.all([import("@opentui/solid/jsx-runtime"), import("solid-js")]);
|
|
23
|
-
return {
|
|
47
|
+
const [jsxRuntime, solid, opentui] = await Promise.all([import("@opentui/solid/jsx-runtime"), import("solid-js"), import("@opentui/solid")]);
|
|
48
|
+
return {
|
|
49
|
+
jsx: jsxRuntime.jsx,
|
|
50
|
+
createSignal: solid.createSignal,
|
|
51
|
+
createEffect: solid.createEffect,
|
|
52
|
+
createRoot: solid.createRoot,
|
|
53
|
+
useTerminalDimensions: opentui.useTerminalDimensions
|
|
54
|
+
};
|
|
24
55
|
}
|
|
25
56
|
/** OpenCode 2 theme tokens under the names the mission palette reads. */
|
|
26
57
|
export function paletteFromV2Theme(theme, mode) {
|
|
@@ -42,23 +73,54 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
42
73
|
const ui = runtime ?? await hostRuntime();
|
|
43
74
|
const [settings] = context.storage.store("settings", { initial: { motion: true } });
|
|
44
75
|
const motionEnabled = settings.motion !== false;
|
|
76
|
+
const band = readBandOptions(context.options);
|
|
77
|
+
const notify = readNotifyOptions(context.options);
|
|
45
78
|
const directory = context.location?.directory ?? context.data.location.default().directory;
|
|
46
79
|
const root = missionProjectRoot({ directory });
|
|
47
80
|
const readLedger = createLedgerReader();
|
|
48
81
|
const readWork = createWorkReader();
|
|
82
|
+
const colorAllowed = rainbowAllowed(motionEnabled, process.env.NO_COLOR);
|
|
49
83
|
const sessions = new Map();
|
|
50
84
|
const ignitions = new Map();
|
|
51
85
|
const trackedAt = new Map();
|
|
52
86
|
const tracked = [];
|
|
87
|
+
const suggested = new Set();
|
|
88
|
+
const suggestedDays = new Map();
|
|
53
89
|
// Each session's store watcher lives in its own root, so it outlasts the slot render that started it
|
|
54
90
|
// and ends only on eviction or cleanup. After cleanup nothing may arm a timer again.
|
|
55
91
|
const watchers = new Map();
|
|
56
92
|
let disposed = false;
|
|
57
93
|
const [revision, setRevision] = ui.createSignal(0);
|
|
58
94
|
const [clock, setClock] = ui.createSignal(Date.now());
|
|
95
|
+
// The version this start loaded; an install newer than it shows `restart to <v>` and one toast.
|
|
96
|
+
const [restartTo, setRestartTo] = ui.createSignal(undefined);
|
|
59
97
|
let frameTimer;
|
|
60
98
|
let refreshTimer;
|
|
61
99
|
let pollTimer;
|
|
100
|
+
let liveTimer;
|
|
101
|
+
let bandTimer;
|
|
102
|
+
let restartTimer;
|
|
103
|
+
const readUsage = context.client?.usage?.limits;
|
|
104
|
+
const limits = createUsageLimits({
|
|
105
|
+
read: readUsage === undefined ? undefined : () => readUsage.call(context.client?.usage),
|
|
106
|
+
now: Date.now,
|
|
107
|
+
intervalMs: limitsPollMs,
|
|
108
|
+
onChange: () => setRevision(revision() + 1)
|
|
109
|
+
});
|
|
110
|
+
const noticeTail = createNoticeTail(root === undefined ? "" : createRuntimePaths(root).noticesFile);
|
|
111
|
+
// The provider of the session's latest assistant message decides whether the Go allowance applies to it.
|
|
112
|
+
function goSession(sessionID) {
|
|
113
|
+
const latest = sessionMessagesV2(context.data, sessionID).filter((message) => message.type === "assistant").at(-1);
|
|
114
|
+
return latest?.model?.providerID === goProviderID;
|
|
115
|
+
}
|
|
116
|
+
// Popups the server could not raise (the automatic handoff) arrive as lines in the notices file.
|
|
117
|
+
function showServerNotices() {
|
|
118
|
+
if (disposed)
|
|
119
|
+
return;
|
|
120
|
+
for (const notice of noticeTail.poll().slice(-noticeBurst)) {
|
|
121
|
+
context.ui.toast.show({ title: notice.title, message: notice.message, variant: notice.variant, duration: notice.duration });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
62
124
|
const palette = () => paletteFromV2Theme(context.theme, context.themeMode);
|
|
63
125
|
function span(segment) {
|
|
64
126
|
return ui.jsx("span", {
|
|
@@ -69,17 +131,43 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
69
131
|
function line(segments) {
|
|
70
132
|
return ui.jsx("text", { wrapMode: "none", children: segments.length === 0 ? " " : segments.map(span) });
|
|
71
133
|
}
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
134
|
+
function state() {
|
|
135
|
+
return missionStateFromV2(context.data, directory);
|
|
136
|
+
}
|
|
137
|
+
function parentOf(sessionID) {
|
|
138
|
+
const parent = context.data.session.get?.(sessionID)?.parentID;
|
|
139
|
+
return typeof parent === "string" && parent !== "" ? parent : undefined;
|
|
140
|
+
}
|
|
141
|
+
function width() {
|
|
142
|
+
return ui.useTerminalDimensions?.()().width ?? defaultWidth;
|
|
143
|
+
}
|
|
144
|
+
function rainbowOn(entry, now) {
|
|
145
|
+
return colorAllowed && rainbowShows(rainbowActive(entry.view, entry.card.chips, entry.card.busy), entry.rainbowSeen, entry.lastPromptAt, now);
|
|
146
|
+
}
|
|
147
|
+
function bandStepping(entry) {
|
|
148
|
+
if (!colorAllowed)
|
|
149
|
+
return false;
|
|
150
|
+
if (entry.waiting || rainbowOn(entry, Date.now()))
|
|
151
|
+
return true;
|
|
152
|
+
return entry.card.threads?.some((row) => row.status === "working" && row.done === undefined) === true;
|
|
153
|
+
}
|
|
154
|
+
function syncBandTimer() {
|
|
155
|
+
if (disposed)
|
|
156
|
+
return;
|
|
157
|
+
const needed = [...sessions.values()].some(bandStepping);
|
|
158
|
+
if (needed && bandTimer === undefined)
|
|
159
|
+
bandTimer = setInterval(() => {
|
|
160
|
+
setClock(Date.now());
|
|
161
|
+
syncBandTimer();
|
|
162
|
+
}, rainbowTickMs);
|
|
163
|
+
if (!needed && bandTimer !== undefined) {
|
|
164
|
+
clearInterval(bandTimer);
|
|
165
|
+
bandTimer = undefined;
|
|
166
|
+
}
|
|
79
167
|
}
|
|
80
168
|
function anyMoving(now) {
|
|
81
169
|
for (const entry of sessions.values())
|
|
82
|
-
if (sampleMotion(entry.motion, now).moving)
|
|
170
|
+
if (sampleMotion(entry.motion, now).moving || chipShimmer(entry.chipMotion, now) !== undefined)
|
|
83
171
|
return true;
|
|
84
172
|
return false;
|
|
85
173
|
}
|
|
@@ -95,11 +183,103 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
95
183
|
}
|
|
96
184
|
}, motionTiming.frameMs);
|
|
97
185
|
}
|
|
186
|
+
// The live step's elapsed time is the one steady change: a tick a second while a tool runs.
|
|
187
|
+
function syncLiveTick() {
|
|
188
|
+
if (disposed)
|
|
189
|
+
return;
|
|
190
|
+
const running = [...sessions.values()].some((entry) => entry.view.live !== undefined);
|
|
191
|
+
if (running && liveTimer === undefined)
|
|
192
|
+
liveTimer = setInterval(() => setClock(Date.now()), liveTickMs);
|
|
193
|
+
if (!running && liveTimer !== undefined) {
|
|
194
|
+
clearInterval(liveTimer);
|
|
195
|
+
liveTimer = undefined;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// The card keeps the tallest lane count it reached while lanes are live or a turn runs, so the prompt does not jump.
|
|
199
|
+
function holdRows(held, rows, busy) {
|
|
200
|
+
if (rows > 0)
|
|
201
|
+
return Math.max(held, rows);
|
|
202
|
+
return busy ? held : 0;
|
|
203
|
+
}
|
|
98
204
|
function ignitionToast(sessionID, name) {
|
|
99
205
|
const label = `${ignitionLine(name)} ๐ฅ`;
|
|
100
206
|
const message = supportsMarkGlyphs() ? [...micro.map((row) => row.replace(/ +$/u, "")), label].join("\n") : label;
|
|
101
207
|
context.ui.toast.show({ title: "๐ฅ LIT IGNITED", message, variant: "warning", duration: ignitionToastMs, sessionID });
|
|
102
208
|
}
|
|
209
|
+
// The finish alert is off unless the plugin options opt in; OpenCode's own attention settings still
|
|
210
|
+
// decide whether a desktop notification or a sound actually plays.
|
|
211
|
+
function alertFinish(notice) {
|
|
212
|
+
if (!notify.enabled || context.attention === undefined)
|
|
213
|
+
return;
|
|
214
|
+
void context.attention
|
|
215
|
+
.notify({ title: "๐ฅ LIT complete", message: notice.message, notification: notify.desktop, sound: notify.sound ? { name: "done" } : false })
|
|
216
|
+
.catch(() => undefined);
|
|
217
|
+
}
|
|
218
|
+
async function checkRestart() {
|
|
219
|
+
const loaded = await readPackageMetadata().catch(() => undefined);
|
|
220
|
+
if (loaded === undefined || disposed)
|
|
221
|
+
return;
|
|
222
|
+
const target = restartVersion(loaded.version, await readInstalledVersions(loaded.packageRoot).catch(() => []));
|
|
223
|
+
if (disposed || target === undefined || target === restartTo())
|
|
224
|
+
return;
|
|
225
|
+
if (restartTo() === undefined) {
|
|
226
|
+
context.ui.toast.show({ variant: "info", title: "๐ฅ LIT", message: `LitOpenCode ${target} is installed. Restart OpenCode to load it.`, duration: toastMs });
|
|
227
|
+
}
|
|
228
|
+
setRestartTo(target);
|
|
229
|
+
}
|
|
230
|
+
// One toast per session, for a fresh root session in a workspace that has earlier work to pick up.
|
|
231
|
+
function suggestAdhd(sessionID, source) {
|
|
232
|
+
if (suggested.has(sessionID) || parentOf(sessionID) !== undefined)
|
|
233
|
+
return;
|
|
234
|
+
suggested.add(sessionID);
|
|
235
|
+
const created = context.data.session.get?.(sessionID)?.time?.created;
|
|
236
|
+
const now = Date.now();
|
|
237
|
+
if (source.session.messages(sessionID).length > 0 || typeof created !== "number" || now - created > 2 * 60_000)
|
|
238
|
+
return;
|
|
239
|
+
const projectRoot = missionProjectRoot(source.path);
|
|
240
|
+
if (projectRoot === undefined || !hasEarlierWork(projectRoot, sessionID, now, earlierWorkWindowMs) || !adhdSuggestDue(suggestedDays, projectRoot, now))
|
|
241
|
+
return;
|
|
242
|
+
context.ui.toast.show({ variant: "info", title: "๐ฅ LIT", message: "Earlier work here. Say lit adhd to pick up where you left off.", duration: toastMs });
|
|
243
|
+
}
|
|
244
|
+
// One session's view, card and the notices from its previous entry. Pure reads; nothing is armed here.
|
|
245
|
+
function build(sessionID, entry, now) {
|
|
246
|
+
const source = state();
|
|
247
|
+
const work = readWork(root, sessionID);
|
|
248
|
+
const research = readLatestResearchRun(root);
|
|
249
|
+
const view = buildMissionView(readMissionInput(source, sessionID, readLedger(root), now, {
|
|
250
|
+
...(work === undefined ? {} : { work }),
|
|
251
|
+
...(research === undefined ? {} : { research })
|
|
252
|
+
}));
|
|
253
|
+
const threads = threadsOf(readThreadSources(source, sessionID, root, now), now);
|
|
254
|
+
const times = messageTimes(source, sessionID);
|
|
255
|
+
const card = {
|
|
256
|
+
...readCardInput(source, sessionID),
|
|
257
|
+
...(threads === undefined ? {} : { threads }),
|
|
258
|
+
...(wayBackDue(times.lastAt, now, hasEarlierWork(root, sessionID, now, earlierWorkWindowMs)) ? { earlier: true } : {})
|
|
259
|
+
};
|
|
260
|
+
const viewChanged = entry === undefined || JSON.stringify(entry.view) !== JSON.stringify(view);
|
|
261
|
+
const rainbowSeen = rainbowActive(view, card.chips, card.busy) ? now : entry?.rainbowSeen;
|
|
262
|
+
const waiting = waitingOnYou(view, card.busy, { asked: card.asked === true, userSpokeLast: card.userSpokeLast === true, blockedReply: card.blockedReply === true, boundary: work?.boundary === true });
|
|
263
|
+
const changed = entry === undefined || viewChanged || entry.lastPromptAt !== times.lastPromptAt || JSON.stringify(entry.card) !== JSON.stringify(card) || entry.rainbowSeen !== rainbowSeen || entry.waiting !== waiting;
|
|
264
|
+
const lanesChanged = entry !== undefined && JSON.stringify(entry.card.threads) !== JSON.stringify(card.threads);
|
|
265
|
+
const notices = entry !== undefined && (viewChanged || lanesChanged)
|
|
266
|
+
? [...missionNotices(entry.view, view), ...v2StageNotices(entry.view, view, entry.card.threads ?? [], card.threads ?? [])]
|
|
267
|
+
: [];
|
|
268
|
+
return {
|
|
269
|
+
changed,
|
|
270
|
+
notices,
|
|
271
|
+
next: {
|
|
272
|
+
view,
|
|
273
|
+
card,
|
|
274
|
+
motion: entry !== undefined && viewChanged ? planMotion(entry.view, view, now, entry.motion, motionEnabled) : (entry?.motion ?? idleMotion),
|
|
275
|
+
chipMotion: planChipMotion(entry?.card.chips, card.chips, now, entry?.chipMotion ?? idleChipMotion, motionEnabled),
|
|
276
|
+
reserve: holdRows(entry?.reserve ?? 0, threadLineCount(threads), card.busy),
|
|
277
|
+
...(rainbowSeen === undefined ? {} : { rainbowSeen }),
|
|
278
|
+
...(times.lastPromptAt === undefined ? {} : { lastPromptAt: times.lastPromptAt }),
|
|
279
|
+
waiting
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
103
283
|
function refresh(sessionID) {
|
|
104
284
|
if (disposed)
|
|
105
285
|
return;
|
|
@@ -110,19 +290,25 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
110
290
|
if (mark !== undefined && mark.at >= (trackedAt.get(sessionID) ?? 0) - historySlackMs)
|
|
111
291
|
ignitionToast(sessionID, mark.name);
|
|
112
292
|
}
|
|
113
|
-
|
|
293
|
+
if (goSession(sessionID))
|
|
294
|
+
void limits.poll();
|
|
114
295
|
const entry = sessions.get(sessionID);
|
|
115
|
-
|
|
296
|
+
const { next, changed, notices } = build(sessionID, entry, now);
|
|
297
|
+
if (!changed)
|
|
116
298
|
return;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
299
|
+
const notice = leadingNotice(notices);
|
|
300
|
+
if (notice !== undefined)
|
|
301
|
+
context.ui.toast.show({ variant: notice.variant, title: notice.title, message: notice.message, duration: toastMs, sessionID });
|
|
302
|
+
const finish = notices.find((candidate) => candidate.celebrate === true);
|
|
303
|
+
if (finish !== undefined)
|
|
304
|
+
alertFinish(finish);
|
|
305
|
+
sessions.set(sessionID, next);
|
|
123
306
|
setClock(now);
|
|
124
307
|
setRevision(revision() + 1);
|
|
125
308
|
ensureFrames();
|
|
309
|
+
syncBandTimer();
|
|
310
|
+
syncLiveTick();
|
|
311
|
+
syncPoll();
|
|
126
312
|
}
|
|
127
313
|
function syncPoll() {
|
|
128
314
|
if (disposed)
|
|
@@ -141,6 +327,7 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
141
327
|
return;
|
|
142
328
|
for (const sessionID of tracked)
|
|
143
329
|
refresh(sessionID);
|
|
330
|
+
showServerNotices();
|
|
144
331
|
syncPoll();
|
|
145
332
|
}
|
|
146
333
|
function scheduleRefresh() {
|
|
@@ -164,8 +351,13 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
164
351
|
const now = Date.now();
|
|
165
352
|
trackedAt.set(sessionID, now);
|
|
166
353
|
ignitions.set(sessionID, latestLitMarkV2(context.data, sessionID)?.messageID);
|
|
167
|
-
|
|
168
|
-
|
|
354
|
+
suggestAdhd(sessionID, state());
|
|
355
|
+
sessions.set(sessionID, build(sessionID, undefined, now).next);
|
|
356
|
+
queueMicrotask(() => {
|
|
357
|
+
syncPoll();
|
|
358
|
+
syncLiveTick();
|
|
359
|
+
syncBandTimer();
|
|
360
|
+
});
|
|
169
361
|
// Opening a session loads its messages over HTTP, which raises no event. Reading the host's reactive
|
|
170
362
|
// store here re-runs this effect as they arrive, so a resumed session lights up too.
|
|
171
363
|
const dispose = ui.createRoot((release) => {
|
|
@@ -184,6 +376,61 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
184
376
|
if (type.startsWith("session.") || type.startsWith("permission.") || type.startsWith("form."))
|
|
185
377
|
scheduleRefresh();
|
|
186
378
|
});
|
|
379
|
+
// Reads that only a rendered slot needs. `view` follows the reactive revision and the clock.
|
|
380
|
+
function snapshot(sessionID) {
|
|
381
|
+
revision();
|
|
382
|
+
const entry = sessions.get(sessionID);
|
|
383
|
+
const now = clock();
|
|
384
|
+
return entry === undefined ? undefined : { ...entry, frame: sampleMotion(entry.motion, now), now };
|
|
385
|
+
}
|
|
386
|
+
function statusCardRows(sessionID, inLane, columns) {
|
|
387
|
+
const current = snapshot(sessionID);
|
|
388
|
+
if (current === undefined)
|
|
389
|
+
return undefined;
|
|
390
|
+
const colors = palette();
|
|
391
|
+
const cells = columns - 2;
|
|
392
|
+
const last = current.view.active ? undefined : lastMission(readLedger(root));
|
|
393
|
+
const shimmer = chipShimmer(current.chipMotion, current.now);
|
|
394
|
+
const lines = statusCard({
|
|
395
|
+
view: current.view,
|
|
396
|
+
frame: current.frame,
|
|
397
|
+
chips: current.card.chips,
|
|
398
|
+
busy: current.card.busy,
|
|
399
|
+
motion: motionEnabled,
|
|
400
|
+
reserve: current.reserve,
|
|
401
|
+
...(current.card.threads === undefined ? {} : { threads: current.card.threads }),
|
|
402
|
+
...(current.card.earlier === true ? { earlier: true } : {}),
|
|
403
|
+
...(current.waiting ? { turn: { glow: turnGlow(current.now, colorAllowed) } } : {}),
|
|
404
|
+
...(rainbowOn(current, current.now) ? { rainbow: rainbowAt(current.now) } : {}),
|
|
405
|
+
...(current.card.lanes !== undefined && activeSkillLook(current.view, current.card.chips, current.card.busy) === "review" ? { lanes: current.card.lanes } : {}),
|
|
406
|
+
...(shimmer === undefined ? {} : { shimmer }),
|
|
407
|
+
figures: {
|
|
408
|
+
contextPercent: current.card.contextPercent,
|
|
409
|
+
...(current.card.branch === undefined ? {} : { branch: current.card.branch }),
|
|
410
|
+
inLane,
|
|
411
|
+
...(goSession(sessionID) && limits.windows().length > 0 ? { limits: limits.windows() } : {}),
|
|
412
|
+
...(restartTo() === undefined ? {} : { restartTo: restartTo() })
|
|
413
|
+
},
|
|
414
|
+
...(last === undefined ? {} : { last })
|
|
415
|
+
}, colors, cells);
|
|
416
|
+
const rows = lines.map(line);
|
|
417
|
+
const detail = band.card ? bandCard(current.view, current.frame, colors, cells) : undefined;
|
|
418
|
+
if (detail !== undefined) {
|
|
419
|
+
rows.push(ui.jsx("box", {
|
|
420
|
+
border: true,
|
|
421
|
+
borderStyle: "rounded",
|
|
422
|
+
borderColor: detail.border,
|
|
423
|
+
title: detail.title,
|
|
424
|
+
titleColor: colors.text,
|
|
425
|
+
paddingLeft: 1,
|
|
426
|
+
paddingRight: 1,
|
|
427
|
+
flexShrink: 0,
|
|
428
|
+
flexDirection: "column",
|
|
429
|
+
children: detail.rows.map(line)
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
return rows;
|
|
433
|
+
}
|
|
187
434
|
const releases = [
|
|
188
435
|
context.ui.slot({
|
|
189
436
|
append: "prompt.footer.status",
|
|
@@ -192,14 +439,30 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
192
439
|
if (sessionID === undefined)
|
|
193
440
|
return null;
|
|
194
441
|
track(sessionID);
|
|
442
|
+
const columns = width;
|
|
195
443
|
return () => {
|
|
196
444
|
revision();
|
|
197
445
|
const mark = latestLitMarkV2(context.data, sessionID);
|
|
198
446
|
if (mark === undefined)
|
|
199
447
|
return null;
|
|
448
|
+
const current = snapshot(sessionID);
|
|
449
|
+
// The sidebar setting is not readable here, so a wide terminal is taken to have it open.
|
|
450
|
+
const sidebar = parentOf(sessionID) === undefined && columns() > sidebarMinWidth;
|
|
451
|
+
const footer = footerStatus({
|
|
452
|
+
name: mark.name,
|
|
453
|
+
view: current?.view,
|
|
454
|
+
frame: current?.frame ?? sampleMotion(idleMotion, clock()),
|
|
455
|
+
palette: palette(),
|
|
456
|
+
budget: footerBudget({ columns: columns(), sidebar, busy: current?.card.busy === true })
|
|
457
|
+
});
|
|
458
|
+
if (footer.length === 0)
|
|
459
|
+
return null;
|
|
200
460
|
return ui.jsx("box", {
|
|
201
|
-
flexShrink:
|
|
202
|
-
|
|
461
|
+
flexShrink: 1,
|
|
462
|
+
minWidth: 0,
|
|
463
|
+
overflow: "hidden",
|
|
464
|
+
flexDirection: "row",
|
|
465
|
+
children: line(footer)
|
|
203
466
|
});
|
|
204
467
|
};
|
|
205
468
|
}
|
|
@@ -235,8 +498,52 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
235
498
|
});
|
|
236
499
|
};
|
|
237
500
|
}
|
|
501
|
+
}),
|
|
502
|
+
context.ui.slot({
|
|
503
|
+
append: "home.footer",
|
|
504
|
+
render() {
|
|
505
|
+
const columns = width;
|
|
506
|
+
return () => {
|
|
507
|
+
revision();
|
|
508
|
+
const segments = homeLine(lastMission(readLedger(root)), palette(), Math.min(homeLineWidth, columns() - 10));
|
|
509
|
+
return segments.length === 0 ? null : ui.jsx("box", { flexDirection: "row", justifyContent: "center", flexShrink: 0, children: line(segments) });
|
|
510
|
+
};
|
|
511
|
+
}
|
|
238
512
|
})
|
|
239
513
|
];
|
|
514
|
+
// The card is drawn once for the whole app, under the session and its sidebar, on every session, lit or
|
|
515
|
+
// not, and keeps its rows when a turn starts. A subagent session shows its parent's card as a lane. The
|
|
516
|
+
// home route has no session, so it shows no card.
|
|
517
|
+
if (band.enabled) {
|
|
518
|
+
releases.push(context.ui.slot({
|
|
519
|
+
append: "app",
|
|
520
|
+
render() {
|
|
521
|
+
const columns = width;
|
|
522
|
+
return () => {
|
|
523
|
+
const route = context.ui.router?.current();
|
|
524
|
+
const sessionID = route?.type === "session" ? route.sessionID : undefined;
|
|
525
|
+
if (sessionID === undefined)
|
|
526
|
+
return null;
|
|
527
|
+
const parent = parentOf(sessionID);
|
|
528
|
+
const target = parent ?? sessionID;
|
|
529
|
+
track(target);
|
|
530
|
+
const rows = statusCardRows(target, parent !== undefined, columns());
|
|
531
|
+
if (rows === undefined)
|
|
532
|
+
return null;
|
|
533
|
+
return ui.jsx("box", {
|
|
534
|
+
flexDirection: "column",
|
|
535
|
+
flexShrink: 0,
|
|
536
|
+
paddingLeft: 1,
|
|
537
|
+
paddingRight: 1,
|
|
538
|
+
backgroundColor: palette().panel ?? "transparent",
|
|
539
|
+
children: rows
|
|
540
|
+
});
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}));
|
|
544
|
+
}
|
|
545
|
+
void checkRestart();
|
|
546
|
+
restartTimer = setInterval(() => void checkRestart(), restartCheckMs);
|
|
240
547
|
return async () => {
|
|
241
548
|
disposed = true;
|
|
242
549
|
for (const dispose of watchers.values())
|
|
@@ -246,12 +553,18 @@ export async function setupLitOpenCodeTui(context, runtime) {
|
|
|
246
553
|
unlisten();
|
|
247
554
|
for (const release of releases)
|
|
248
555
|
release();
|
|
556
|
+
if (restartTimer !== undefined)
|
|
557
|
+
clearInterval(restartTimer);
|
|
249
558
|
if (frameTimer !== undefined)
|
|
250
559
|
clearInterval(frameTimer);
|
|
251
560
|
if (refreshTimer !== undefined)
|
|
252
561
|
clearTimeout(refreshTimer);
|
|
253
562
|
if (pollTimer !== undefined)
|
|
254
563
|
clearInterval(pollTimer);
|
|
564
|
+
if (liveTimer !== undefined)
|
|
565
|
+
clearInterval(liveTimer);
|
|
566
|
+
if (bandTimer !== undefined)
|
|
567
|
+
clearInterval(bandTimer);
|
|
255
568
|
sessions.clear();
|
|
256
569
|
ignitions.clear();
|
|
257
570
|
trackedAt.clear();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type LimitWindow = {
|
|
2
|
+
readonly label: "5h" | "1w" | "1mo";
|
|
3
|
+
readonly percent: number;
|
|
4
|
+
readonly limited: boolean;
|
|
5
|
+
};
|
|
6
|
+
/** Reads the usage-limits reply (`{ data: Info | null }` or the bare Info) into the windows worth drawing. */
|
|
7
|
+
export declare function limitWindows(reply: unknown): readonly LimitWindow[];
|
|
8
|
+
export type UsageLimitsOptions = {
|
|
9
|
+
/** Asks the host; absent when the host has no usage source. */
|
|
10
|
+
readonly read: (() => Promise<unknown>) | undefined;
|
|
11
|
+
readonly now: () => number;
|
|
12
|
+
readonly intervalMs: number;
|
|
13
|
+
readonly onChange: () => void;
|
|
14
|
+
};
|
|
15
|
+
/** A throttled reader: it asks at most once per interval, and a failed or empty answer clears the figures. */
|
|
16
|
+
export declare function createUsageLimits(options: UsageLimitsOptions): {
|
|
17
|
+
readonly windows: () => readonly LimitWindow[];
|
|
18
|
+
readonly poll: () => Promise<void>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// The plan windows OpenCode 2 can tell a plugin about. Its one usage source is client.usage.limits(): the
|
|
2
|
+
// OpenCode Go allowance of the Console workspace the location is signed in to, as a rolling window (five
|
|
3
|
+
// hours), a weekly one and a monthly one, each with a percent used; the reply is null when nobody is signed
|
|
4
|
+
// in. There are no provider response headers or rate-limit events for a plugin to read, so for any other
|
|
5
|
+
// provider this module has nothing to show, and the card draws no figure rather than a made-up one.
|
|
6
|
+
/** The monthly window is shown only when it is close to the cap, so the card keeps LitClaude's 5h and 1w pair. */
|
|
7
|
+
const monthlyShowPercent = 90;
|
|
8
|
+
function record(value) {
|
|
9
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
function window(label, raw) {
|
|
12
|
+
const meter = record(raw);
|
|
13
|
+
const percent = meter?.percent;
|
|
14
|
+
if (typeof percent !== "number" || !Number.isFinite(percent))
|
|
15
|
+
return undefined;
|
|
16
|
+
return { label, percent: Math.round(Math.min(100, Math.max(0, percent))), limited: meter?.limited === true };
|
|
17
|
+
}
|
|
18
|
+
/** Reads the usage-limits reply (`{ data: Info | null }` or the bare Info) into the windows worth drawing. */
|
|
19
|
+
export function limitWindows(reply) {
|
|
20
|
+
const outer = record(reply);
|
|
21
|
+
const info = outer === undefined ? undefined : "data" in outer ? record(outer.data) : outer;
|
|
22
|
+
const allowance = record(info?.go);
|
|
23
|
+
if (allowance === undefined)
|
|
24
|
+
return [];
|
|
25
|
+
const rolling = window("5h", allowance.rolling);
|
|
26
|
+
const weekly = window("1w", allowance.weekly);
|
|
27
|
+
const monthly = window("1mo", allowance.monthly);
|
|
28
|
+
return [
|
|
29
|
+
...(rolling === undefined ? [] : [rolling]),
|
|
30
|
+
...(weekly === undefined ? [] : [weekly]),
|
|
31
|
+
...(monthly !== undefined && (monthly.limited || monthly.percent >= monthlyShowPercent) ? [monthly] : [])
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
/** A throttled reader: it asks at most once per interval, and a failed or empty answer clears the figures. */
|
|
35
|
+
export function createUsageLimits(options) {
|
|
36
|
+
let current = [];
|
|
37
|
+
let askedAt;
|
|
38
|
+
let pending;
|
|
39
|
+
async function ask(read) {
|
|
40
|
+
let next;
|
|
41
|
+
try {
|
|
42
|
+
next = limitWindows(await read());
|
|
43
|
+
}
|
|
44
|
+
catch { // no-excuse-ok: catch -- an unreachable or signed-out host means there is nothing to show, never an error on screen.
|
|
45
|
+
next = [];
|
|
46
|
+
}
|
|
47
|
+
if (JSON.stringify(next) === JSON.stringify(current))
|
|
48
|
+
return;
|
|
49
|
+
current = next;
|
|
50
|
+
options.onChange();
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
windows: () => current,
|
|
54
|
+
poll() {
|
|
55
|
+
const read = options.read;
|
|
56
|
+
if (read === undefined)
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
if (pending !== undefined)
|
|
59
|
+
return pending;
|
|
60
|
+
const now = options.now();
|
|
61
|
+
if (askedAt !== undefined && now - askedAt < options.intervalMs)
|
|
62
|
+
return Promise.resolve();
|
|
63
|
+
askedAt = now;
|
|
64
|
+
pending = ask(read).finally(() => {
|
|
65
|
+
pending = undefined;
|
|
66
|
+
});
|
|
67
|
+
return pending;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.38"><title>release: 1.0.38</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.38</text></g></svg>
|
package/docs/reference-Ko-KR.md
CHANGED
|
@@ -380,10 +380,29 @@ Code Mode ๋ฐ, ๋ชจ๋ธ์ ๋๊ตฌ ๋ชฉ๋ก์ ๋ฐ๋ก ์์ผ๋ฏ๋ก `lit-plan`๋ ๊ณ
|
|
|
380
380
|
lit ํ๋กฌํํธ์ด๋ฉด ํ๋กฌํํธ ์๋ ์ค(`prompt.footer.status`)์ `๐ฅ LIT IGNITED ยท <discipline>`์ด ๋์ต๋๋ค.
|
|
381
381
|
OpenCode 1์์๋ ํ์๊ฐ ์์ ์๋ฆฌ์ ๊ทธ๋๋ก ์์ต๋๋ค.
|
|
382
382
|
- *ํ ์คํธ.* OpenCode 2์์๋ ์๋ฒ๊ฐ ํ ์คํธ๋ฅผ ๋์ธ ์ ์์ต๋๋ค. ์๋ฒ๊ฐ ์ฌ์ฉ์ ๋ฉ์์ง ๋ฉํ๋ฐ์ดํฐ์ ๋จ๊ธด lit ํ์๋ฅผ
|
|
383
|
-
TUI๊ฐ ๋ณด๊ณ ์ ํ ํ ์คํธ๋ฅผ ์ง์
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
383
|
+
TUI๊ฐ ๋ณด๊ณ ์ ํ ํ ์คํธ๋ฅผ ์ง์ ๋์ฐ๊ณ , OpenCode 1๊ณผ ๊ฐ์ ๊ด๋ฌธ ํ ์คํธ(๊ณํ ์ ์ฅ, ๋ชฉํ ์ค์ , ๊ธฐ์ค ํต๊ณผ, ๋งํ, ์๋ฃ)์
|
|
384
|
+
๋ํด OpenCode 2์๋ง ์๋ ์ธ ๊ฐ์ง๋ฅผ ๋์๋๋ค. ์์
๋จ๊ณ๊ฐ ๋์ด๊ฐ ๋(`๐ฅ Stage`, `work 3/7`), ๊ท์จ์ด ๋ฐ๋ ๋
|
|
385
|
+
(`๐ฅ lit-loop โ start-work`), ๋ ์ธ์ด ์์๋๊ฑฐ๋ ๋๋ ๋์
๋๋ค. ํ๊บผ๋ฒ์ ์ผ์ด๋ ์ผ์ `+N more`๊ฐ ๋ถ์ ํ ์คํธ
|
|
386
|
+
ํ๋๋ก ํฉ์นฉ๋๋ค. ์ฌ์์ ์๋ด, `lit adhd` ์ ์, ์ผ ๋๋ฉด ๋์ค๋ ์๋ฃ ์๋ฆผ์ OpenCode 1๊ณผ ๊ฐ์ต๋๋ค. ์๋ฒ๋ง ์๋
|
|
387
|
+
์๋ ์ธ์์ธ๊ณ ์๋ฆผ์ `.litopencode/notices.jsonl`์ ๊ฑฐ์ณ ํ๋ฉด์ ๋์ต๋๋ค. OpenCode 2 ์๋ฒ ํ๋ฌ๊ทธ์ธ์ ์ด๋ฒคํธ๋ฅผ
|
|
388
|
+
๊ตฌ๋
ํ ์๋ง ์๊ณ ํ ์คํธ๋ฅผ ๋์ธ ์ ์์ผ๋ฏ๋ก, ์๋ฒ๊ฐ ์๋ฆผ์ ๊ทธ ํ์ผ์ ํ ์ค์ฉ ๋ง๋ถ์ด๋ฉด TUI ํ๋ฌ๊ทธ์ธ์ด ์์ํ ๋ค์
|
|
389
|
+
์๊ธด ์ค์ ํ ์คํธ๋ก ๋ณด์ฌ ์ค๋๋ค(ํ์ผ ํฌ๊ธฐ์ ์ํ์ด ์๊ณ ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ ๋ฐ๋ผ๊ฐ์ง ์์ต๋๋ค). ์๋ฒ๊ฐ ๋์ฐ๋ ๋๋จธ์ง
|
|
390
|
+
ํ๋์ธ ์ ํ ํ ์คํธ๋ ์ด๋ฏธ TUI๊ฐ ์ง์ ๋์ฐ๋ฏ๋ก, ๋ฐ๋ก ์ฎ๊ธธ ์คํฌ ํํธ๋ ๋จ์ ์์ง ์์ต๋๋ค.
|
|
391
|
+
- *TUI.* HUD๋ OpenCode 2๊ฐ ๊ณต๊ฐํ ์ฌ๋กฏ์ ๋์
๋๋ค. ์ํ ์นด๋๋ `app` ์ฌ๋กฏ์์ ์ธ์
๊ณผ ์ฌ์ด๋๋ฐ ์๋์ ๊ทธ๋ ค์ง๊ณ ,
|
|
392
|
+
์ฌ๋ ์ผํ๋ ๋ค ์ค์ ์ ์งํฉ๋๋ค. ๋จ๊ณ ์นฉ์ ์์ด์ ํธยท๋ชจ๋ธ ์ค ์ค๋ฅธ์ชฝ์ ์ฌ๋กฏ์ด ์์ด์ ์ ํ ์ค๊ณผ ํจ๊ป
|
|
393
|
+
`prompt.footer.status`์ ์๊ณ , ํ ์ค์ `home.footer`, ๋ฏธ์
ํจ๋์ `sidebar.content`์ ์์ผ๋ฏ๋ก ์ฌ์ด๋๋ฐ๋ฅผ ์ด์ด
|
|
394
|
+
๋๋ฉด ๋ณด์
๋๋ค. ์นด๋, ์นฉ, ํจ๋, ํ ์ค์ OpenCode 1๊ณผ ๊ฐ์ ๊ธ์ ๋น๋๋ฅผ ์๋๋ค. ํ๋ฌ๊ทธ์ธ ํญ๋ชฉ์ `band`, `notify`
|
|
395
|
+
์ต์
์ OpenCode 2๊ฐ ๋๊ฒจ ์ฃผ๋ ์ต์
์์ ์ฝ์ต๋๋ค. ํธํฐ ์ค์ ํธ์คํธ๊ฐ ์๋ผ ๋ฒ๋ฆฌ๊ฒ ๋์ง ์๊ณ ํฐ๋ฏธ๋ ๋๋น์ ๋ง์ถฐ
|
|
396
|
+
๋จ๊ณ์ ์ผ๋ก ์ค์ด๋ญ๋๋ค. ํ์์ ์ ์ฒด ์นฉ, ํ์์ ์์ ์นฉ, ์นฉ๋ง, `๐ฅ 1/3`, ๋ถ๊ฝ๋ง, ์๋ฌด๊ฒ๋ ์์ ์์์
๋๋ค. ์ธ ์ ์๋
|
|
397
|
+
๋๋น๋ ์ ์ฒด ๋๋น์์ ์ฌ์ด๋๋ฐ(120์นธ์ ๋์ผ๋ฉด ์ด๋ ค ์๋ค๊ณ ๊ฐ์ )์ ํธ์คํธ ์์ ์ ์คํผ๋ยท์์น๋ฅผ ๋บ ๊ฐ์
๋๋ค.
|
|
398
|
+
OpenCode 2๋ ํ ์ผ ๋ชฉ๋ก์ ๋์ง ์์ผ๋ฏ๋ก ๋ฏธ์
์งํ์ ledger์
|
|
399
|
+
์์
๊ธฐ๋ก๊ณผ ๊ณํ์์ ์ฝ์ต๋๋ค. ๋๊ตฌ ์ด๋ฆ๋ ๋ค๋ฆ
๋๋ค(`shell`, `subagent`, `id`๋ฅผ ๋ฐ๋ `skill` ๋๊ตฌ). ์นด๋๋ ๊ทธ
|
|
400
|
+
์ด๋ฆ์ ์ฝ์ต๋๋ค. OpenCode 2๊ฐ ํ๋ฌ๊ทธ์ธ์ ์ฃผ๋ ์ฌ์ฉ๋ ์ถ์ฒ๋ `client.usage.limits()` ํ๋์
๋๋ค. ์์น๊ฐ ๋ก๊ทธ์ธํ
|
|
401
|
+
OpenCode Console ์์
๊ณต๊ฐ์ OpenCode Go ํ์ฉ๋(5์๊ฐ ๋กค๋ง, ์ฃผ๊ฐ, ์๊ฐ ์ฐฝ)์
๋๋ค. ๋ง์ง๋ง ๋ต๋ณ์ด `opencode-go`
|
|
402
|
+
์ ๊ณต์์์ ์จ ์ธ์
์ด๋ฉด ์นด๋ ์๋ ํ
๋๋ฆฌ์ ์ปจํ
์คํธ ์์น ๋ค๋ก `5h 42% ยท 1w 18%`๊ฐ ๋์ต๋๋ค(์๊ฐ ์ฐฝ์ 90% ์ด์์ผ
|
|
403
|
+
๋๋ง, ํธ์คํธ๊ฐ ์์ฒญ์ ๊ฑฐ๋ถ ์ค์ด๋ผ๊ณ ์๋ฆฌ๋ ์ฐฝ์ `limited`๋ก ํ์). ํธ์ถ์ 1๋ถ์ ํ ๋ฒ ์ดํ์
๋๋ค. ๋ค๋ฅธ ์ ๊ณต์,
|
|
404
|
+
๋ก๊ทธ์์ ์ํ(์๋ต์ด `null`), ์ค๋ฅ์ผ ๋๋ ์๋ฌด๊ฒ๋ ๊ทธ๋ฆฌ์ง ์์ต๋๋ค. OpenCode 2๋ ์ ๊ณต์ ์๋ ์ ํ ํค๋๋ ์ด๋ฒคํธ๋ฅผ
|
|
405
|
+
ํ๋ฌ๊ทธ์ธ์ ๋
ธ์ถํ์ง ์์ผ๋ฏ๋ก AnthropicยทOpenAI ์๊ธ์ ์ 5์๊ฐยท์ฃผ๊ฐ ์์น๋ ์์ต๋๋ค.
|
|
387
406
|
- *์
๋ฐ์ดํธ.* OpenCode 2์์๋ ํ๋ฌ๊ทธ์ธ ์์ ์๋ ์
๋ฐ์ดํธ๊ฐ ์์ต๋๋ค. `opencode plugin update`๋ฅผ ์คํํ๊ฑฐ๋
|
|
388
407
|
OpenCode 2์ ์
๋ฐ์ดํธ ๋ฐฉ์์ ๋งก๊ธฐ๋ฉด ๋ฉ๋๋ค.
|
|
389
408
|
|
|
@@ -458,7 +477,7 @@ OpenCode 1.18์ `tui.json`์ด๋ `tui.jsonc`์ `plugin` ๋ฐฐ์ด์ ์ ํ TUI
|
|
|
458
477
|
- `lit-adhd`(๋ณ์นญ `lit-rewind`)๊ฐ ๋๋ ๋์ ํ
๋๋ฆฌ, LIT ๊ธ์, ๊ฒ์ด์ง๊ฐ ๊ฐ์ ๋ฐ๊ธฐ์ ์์ํ์ ๋ฐ๋ผ ๋๋๋ค.
|
|
459
478
|
1์ด์ ๋ค ๋ฒ ์์ด ์กฐ๊ธ์ฉ ๋ฐ๋๊ณ ๊น๋ฐ์ด์ง ์์ต๋๋ค. `motion: false`์ `NO_COLOR`์์๋ ๊บผ์ง๋๋ค. ์ด์
|
|
460
479
|
์์
์ด ์๋ ์์
๊ณต๊ฐ์ ์ฌ๋ ์ธ์
์ `lit adhd to pick up where you left off`๋ฅผ ๋ณด์ฌ ์ฃผ๊ณ , ๊ทธ๊ณณ์์ ์ฒ์ ์ฐ
|
|
461
|
-
์ ์ธ์
์ ๊ฐ์ ๋ฌธ๊ตฌ๋ก ํ ์คํธ๋ฅผ ํ ๋ฒ ๋์๋๋ค(
|
|
480
|
+
์ ์ธ์
์ ๊ฐ์ ๋ฌธ๊ตฌ๋ก ํ ์คํธ๋ฅผ ํ ๋ฒ ๋์๋๋ค(OpenCode 1๊ณผ 2 ๋ชจ๋).
|
|
462
481
|
- ์นด๋์ ๋ชจ์์ ํ ๋ฒ์ ํ๋์ด๊ณ , `src/tui/mission-looks.ts`์ ์์ ๋ชฉ๋ก์์ ๊ณ ๋ฆ
๋๋ค. lit-adhd๋ฅผ ์คํํ๋
|
|
463
482
|
๊ธธ์ ๋ชจ๋ ๋๊ฐ๊ธฐ ๋ชจ์์ด ๋ฉ๋๋ค. ์ฑํ
์ `lit adhd`/`lit rewind`, `/lit-adhd`/`/lit-rewind`(v2์ ์ค์น๋ ๋ณ์นญ
|
|
464
483
|
ํ์ผ์์ ์จ ๊ฒ๋ ํฌํจ), ๋ค์ดํฐ๋ธ `skill` ๋ก๋๊ฐ ๊ทธ๋ ์ต๋๋ค. ์ธ์
์ด ์ฌ์ฉ์๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๋์(๊ถํ ์์ฒญ์ด๋ ์ง๋ฌธ,
|