@hraness/dawg 0.0.0-stage → 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/CHANGELOG.md +126 -0
- package/DAWG.md +327 -0
- package/LICENSE +21 -0
- package/README.md +213 -2
- package/core/diff.ts +249 -0
- package/core/drums.ts +102 -0
- package/core/key.ts +43 -0
- package/core/loop.ts +78 -0
- package/core/pitch.ts +60 -0
- package/core/score.ts +1388 -0
- package/core/sdk/eval-child.ts +113 -0
- package/core/sdk/eval.ts +257 -0
- package/core/sdk/print.ts +393 -0
- package/core/sdk/v1.ts +954 -0
- package/core/slug.ts +19 -0
- package/package.json +45 -4
- package/src/agent/agent.ts +853 -0
- package/src/agent/brief.ts +160 -0
- package/src/agent/gateway.ts +441 -0
- package/src/agent/models.ts +633 -0
- package/src/agent/ops.ts +157 -0
- package/src/agent/planner.ts +259 -0
- package/src/agent/provider.ts +454 -0
- package/src/agent/sse.ts +114 -0
- package/src/agent/tools.ts +1373 -0
- package/src/agent/usage.ts +296 -0
- package/src/agent/workspace.ts +683 -0
- package/src/agent/xcb-agent.ts +262 -0
- package/src/agent/xcb.ts +579 -0
- package/src/audio/click.ts +125 -0
- package/src/audio/clock.ts +68 -0
- package/src/audio/engine.ts +841 -0
- package/src/audio/live.ts +152 -0
- package/src/audio/lock.ts +57 -0
- package/src/audio/player.ts +134 -0
- package/src/audio/render-worker.ts +68 -0
- package/src/audio/renderer.ts +174 -0
- package/src/audio/sampler.ts +292 -0
- package/src/audio/samples.ts +683 -0
- package/src/audio/wav.ts +861 -0
- package/src/auth/cli.ts +231 -0
- package/src/auth/credentials.ts +411 -0
- package/src/auth/discover.ts +481 -0
- package/src/auth/login.ts +1191 -0
- package/src/auth/openrouter.ts +206 -0
- package/src/auth/picker.ts +282 -0
- package/src/auth/runner.ts +207 -0
- package/src/auth/tui.ts +107 -0
- package/src/commands/edit.ts +170 -0
- package/src/commands/help.ts +247 -0
- package/src/commands/history.ts +69 -0
- package/src/commands/music.ts +461 -0
- package/src/commands/sample.ts +302 -0
- package/src/daemon.ts +31 -0
- package/src/main.ts +2209 -0
- package/src/media/analyze.ts +364 -0
- package/src/media/backend.ts +253 -0
- package/src/media/cli.ts +173 -0
- package/src/media/download.ts +281 -0
- package/src/media/dsp.ts +281 -0
- package/src/media/import.ts +130 -0
- package/src/media/lyrics.ts +201 -0
- package/src/media/notes.ts +363 -0
- package/src/media/paths.ts +168 -0
- package/src/media/process.ts +226 -0
- package/src/media/registry.ts +9 -0
- package/src/media/sidecar.ts +72 -0
- package/src/media/stemdeck.ts +254 -0
- package/src/media/stems.ts +173 -0
- package/src/media/tools.ts +292 -0
- package/src/media/types.ts +92 -0
- package/src/media/vendor/basic-pitch.ts +261 -0
- package/src/media/vendor/drums.ts +817 -0
- package/src/media/vendor/grid.ts +203 -0
- package/src/media/vendor/util.ts +139 -0
- package/src/media/vendor/wav.ts +233 -0
- package/src/project/check.ts +80 -0
- package/src/project/init.ts +253 -0
- package/src/project/sync.ts +432 -0
- package/src/project/typecheck.ts +149 -0
- package/src/render.ts +121 -0
- package/src/session/attach.ts +181 -0
- package/src/session/client.ts +498 -0
- package/src/session/daemon.ts +740 -0
- package/src/session/delta.ts +249 -0
- package/src/session/list.ts +180 -0
- package/src/session/lock.ts +92 -0
- package/src/session/meta.ts +253 -0
- package/src/session/naming.ts +430 -0
- package/src/session/port.ts +481 -0
- package/src/session/presence.ts +159 -0
- package/src/session/protocol.ts +618 -0
- package/src/session/rebase.ts +168 -0
- package/src/session/store.ts +581 -0
- package/src/tui/menu.ts +1083 -0
- package/src/tui/play-mode.ts +442 -0
- package/src/tui/play-session.ts +636 -0
- package/src/web/fetch.ts +340 -0
- package/src/web/http.ts +137 -0
- package/src/web/search.ts +681 -0
- package/tui/activity.ts +364 -0
- package/tui/app.ts +1372 -0
- package/tui/drums.ts +65 -0
- package/tui/highway.ts +921 -0
- package/tui/input.ts +63 -0
- package/tui/keys.ts +102 -0
- package/tui/layers.ts +80 -0
- package/tui/play-strip.ts +143 -0
- package/tui/prompt.ts +609 -0
- package/tui/render.ts +124 -0
- package/tui/screen.ts +247 -0
- package/tui/text.ts +72 -0
- package/tui/theme.ts +451 -0
package/tui/activity.ts
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity feed: the strip under the hit line and the transcript overlay.
|
|
3
|
+
*
|
|
4
|
+
* Other subsystems describe what happened; the TUI decides how to show it.
|
|
5
|
+
* - Command handlers push receipts (`pushCard`) and errors (`pushError`).
|
|
6
|
+
* - The agent lane forwards streaming events through `applyAgentEvent`.
|
|
7
|
+
* - A spinner (`setSpinner`) shows that work is in flight.
|
|
8
|
+
*
|
|
9
|
+
* The feed is plain data with an injectable clock, so tests can assert exact
|
|
10
|
+
* strip contents without timers.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type CardTone = "info" | "success" | "warning" | "error" | "agent";
|
|
14
|
+
|
|
15
|
+
export interface ActivityCard {
|
|
16
|
+
id: number;
|
|
17
|
+
/** Short operation summary, e.g. "+8 bass notes". */
|
|
18
|
+
text: string;
|
|
19
|
+
tone: CardTone;
|
|
20
|
+
atMs: number;
|
|
21
|
+
baseRevision?: number | undefined;
|
|
22
|
+
resultRevision?: number | undefined;
|
|
23
|
+
trackId?: string | undefined;
|
|
24
|
+
/** Key hint shown after the card, e.g. "u undo". */
|
|
25
|
+
hint?: string | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type TranscriptKind =
|
|
29
|
+
"request" | "op" | "revision" | "error" | "agent" | "note";
|
|
30
|
+
|
|
31
|
+
export interface TranscriptEntry {
|
|
32
|
+
id: number;
|
|
33
|
+
kind: TranscriptKind;
|
|
34
|
+
text: string;
|
|
35
|
+
atMs: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Streaming agent events. Structurally compatible with `AgentEvent` from
|
|
40
|
+
* src/agent/agent.ts (`delta`, `diagnostic`, `text`, `code`), so the agent lane
|
|
41
|
+
* forwards events unchanged; the shorter aliases (`text`, `reason`, `summary`)
|
|
42
|
+
* are accepted for other producers.
|
|
43
|
+
*/
|
|
44
|
+
export type AgentActivityEvent =
|
|
45
|
+
| { type: "start"; prompt?: string | undefined; model?: string | undefined }
|
|
46
|
+
| { type: "step"; step: number }
|
|
47
|
+
| { type: "text-delta"; delta?: string; text?: string }
|
|
48
|
+
| { type: "activity"; message: string }
|
|
49
|
+
| { type: "tool-start"; name: string; callId?: string; step?: number }
|
|
50
|
+
| { type: "tool-progress"; line: string; name?: string; callId?: string }
|
|
51
|
+
| {
|
|
52
|
+
type: "tool-applied";
|
|
53
|
+
summary: string;
|
|
54
|
+
name?: string;
|
|
55
|
+
callId?: string;
|
|
56
|
+
baseRevision?: number | undefined;
|
|
57
|
+
resultRevision?: number | undefined;
|
|
58
|
+
trackId?: string | undefined;
|
|
59
|
+
}
|
|
60
|
+
| {
|
|
61
|
+
type: "tool-rejected";
|
|
62
|
+
name?: string;
|
|
63
|
+
callId?: string;
|
|
64
|
+
diagnostic?: string;
|
|
65
|
+
summary?: string | undefined;
|
|
66
|
+
reason?: string;
|
|
67
|
+
}
|
|
68
|
+
| {
|
|
69
|
+
type: "done";
|
|
70
|
+
summary?: string | undefined;
|
|
71
|
+
text?: string;
|
|
72
|
+
applied?: number;
|
|
73
|
+
rejected?: number;
|
|
74
|
+
revision?: number;
|
|
75
|
+
}
|
|
76
|
+
| {
|
|
77
|
+
type: "error";
|
|
78
|
+
message: string;
|
|
79
|
+
code?: string;
|
|
80
|
+
applied?: number;
|
|
81
|
+
revision?: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export interface ActivityOptions {
|
|
85
|
+
clock?: () => number;
|
|
86
|
+
/** Cards kept for the strip. */
|
|
87
|
+
maxCards?: number;
|
|
88
|
+
/** Entries kept for the transcript overlay. */
|
|
89
|
+
maxTranscript?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const BRAILLE_SPINNER = [
|
|
93
|
+
"⠋",
|
|
94
|
+
"⠙",
|
|
95
|
+
"⠹",
|
|
96
|
+
"⠸",
|
|
97
|
+
"⠼",
|
|
98
|
+
"⠴",
|
|
99
|
+
"⠦",
|
|
100
|
+
"⠧",
|
|
101
|
+
"⠇",
|
|
102
|
+
"⠏",
|
|
103
|
+
] as const;
|
|
104
|
+
export const ASCII_SPINNER = ["-", "\\", "|", "/"] as const;
|
|
105
|
+
|
|
106
|
+
export function spinnerFrame(
|
|
107
|
+
nowMs: number,
|
|
108
|
+
unicode: boolean,
|
|
109
|
+
reducedMotion = false,
|
|
110
|
+
): string {
|
|
111
|
+
const frames = unicode ? BRAILLE_SPINNER : ASCII_SPINNER;
|
|
112
|
+
if (reducedMotion) return unicode ? "…" : "~";
|
|
113
|
+
return frames[Math.floor(nowMs / 80) % frames.length]!;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Format a revision transition, e.g. "rev 41→42". */
|
|
117
|
+
export function revisionLabel(
|
|
118
|
+
base: number | undefined,
|
|
119
|
+
result: number | undefined,
|
|
120
|
+
unicode = true,
|
|
121
|
+
): string | undefined {
|
|
122
|
+
if (result === undefined) return undefined;
|
|
123
|
+
if (base === undefined || base === result) return `rev ${result}`;
|
|
124
|
+
return `rev ${base}${unicode ? "→" : "->"}${result}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class ActivityFeed {
|
|
128
|
+
private nextId = 1;
|
|
129
|
+
private readonly clock: () => number;
|
|
130
|
+
private readonly maxCards: number;
|
|
131
|
+
private readonly maxTranscript: number;
|
|
132
|
+
private cardList: ActivityCard[] = [];
|
|
133
|
+
private log: TranscriptEntry[] = [];
|
|
134
|
+
private spinnerLabel: string | undefined;
|
|
135
|
+
private spinnerSince = 0;
|
|
136
|
+
private streamText = "";
|
|
137
|
+
private queue = 0;
|
|
138
|
+
private listeners = new Set<() => void>();
|
|
139
|
+
/** Increments on every change; renderers can skip work when unchanged. */
|
|
140
|
+
version = 0;
|
|
141
|
+
|
|
142
|
+
constructor(options: ActivityOptions = {}) {
|
|
143
|
+
this.clock = options.clock ?? Date.now;
|
|
144
|
+
this.maxCards = options.maxCards ?? 6;
|
|
145
|
+
this.maxTranscript = options.maxTranscript ?? 200;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get cards(): readonly ActivityCard[] {
|
|
149
|
+
return this.cardList;
|
|
150
|
+
}
|
|
151
|
+
get transcript(): readonly TranscriptEntry[] {
|
|
152
|
+
return this.log;
|
|
153
|
+
}
|
|
154
|
+
get spinner(): { label: string; sinceMs: number } | undefined {
|
|
155
|
+
return this.spinnerLabel === undefined
|
|
156
|
+
? undefined
|
|
157
|
+
: { label: this.spinnerLabel, sinceMs: this.spinnerSince };
|
|
158
|
+
}
|
|
159
|
+
/** Latest streamed agent text (single line, newest tail kept). */
|
|
160
|
+
get streaming(): string {
|
|
161
|
+
return this.streamText;
|
|
162
|
+
}
|
|
163
|
+
get queueDepth(): number {
|
|
164
|
+
return this.queue;
|
|
165
|
+
}
|
|
166
|
+
get latest(): ActivityCard | undefined {
|
|
167
|
+
return this.cardList[this.cardList.length - 1];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
subscribe(listener: () => void): () => void {
|
|
171
|
+
this.listeners.add(listener);
|
|
172
|
+
return () => this.listeners.delete(listener);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
pushCard(
|
|
176
|
+
text: string,
|
|
177
|
+
details: Omit<Partial<ActivityCard>, "id" | "atMs" | "text"> = {},
|
|
178
|
+
): ActivityCard {
|
|
179
|
+
const card: ActivityCard = {
|
|
180
|
+
id: this.nextId++,
|
|
181
|
+
text,
|
|
182
|
+
tone: details.tone ?? "info",
|
|
183
|
+
atMs: this.clock(),
|
|
184
|
+
baseRevision: details.baseRevision,
|
|
185
|
+
resultRevision: details.resultRevision,
|
|
186
|
+
trackId: details.trackId,
|
|
187
|
+
hint: details.hint,
|
|
188
|
+
};
|
|
189
|
+
this.cardList = [...this.cardList, card].slice(-this.maxCards);
|
|
190
|
+
const revision = revisionLabel(card.baseRevision, card.resultRevision);
|
|
191
|
+
this.record(
|
|
192
|
+
card.tone === "error" ? "error" : "op",
|
|
193
|
+
revision ? `${text} · ${revision}` : text,
|
|
194
|
+
);
|
|
195
|
+
this.changed();
|
|
196
|
+
return card;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
pushError(message: string): ActivityCard {
|
|
200
|
+
return this.pushCard(message, { tone: "error" });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Record a user request in the transcript without adding a strip card. */
|
|
204
|
+
pushRequest(text: string): void {
|
|
205
|
+
this.record("request", text);
|
|
206
|
+
this.changed();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
pushNote(text: string, kind: TranscriptKind = "note"): void {
|
|
210
|
+
this.record(kind, text);
|
|
211
|
+
this.changed();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
setSpinner(label: string | undefined): void {
|
|
215
|
+
if (label === this.spinnerLabel) return;
|
|
216
|
+
if (label !== undefined && this.spinnerLabel === undefined)
|
|
217
|
+
this.spinnerSince = this.clock();
|
|
218
|
+
this.spinnerLabel = label;
|
|
219
|
+
if (label === undefined) this.streamText = "";
|
|
220
|
+
this.changed();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
setQueueDepth(depth: number): void {
|
|
224
|
+
const next = Math.max(0, Math.floor(depth));
|
|
225
|
+
if (next === this.queue) return;
|
|
226
|
+
this.queue = next;
|
|
227
|
+
this.changed();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Adapter for the agent lane's streaming events. */
|
|
231
|
+
applyAgentEvent(event: AgentActivityEvent): void {
|
|
232
|
+
switch (event.type) {
|
|
233
|
+
case "start":
|
|
234
|
+
this.streamText = "";
|
|
235
|
+
if (event.prompt) this.record("request", event.prompt);
|
|
236
|
+
this.setSpinner(event.model ? `thinking · ${event.model}` : "thinking");
|
|
237
|
+
return;
|
|
238
|
+
case "step":
|
|
239
|
+
this.setSpinner(
|
|
240
|
+
event.step <= 1 ? "thinking" : `thinking · step ${event.step}`,
|
|
241
|
+
);
|
|
242
|
+
return;
|
|
243
|
+
case "activity":
|
|
244
|
+
this.setSpinner(event.message);
|
|
245
|
+
return;
|
|
246
|
+
case "text-delta": {
|
|
247
|
+
const chunk = event.delta ?? event.text ?? "";
|
|
248
|
+
const joined = `${this.streamText}${chunk}`.replace(/\s+/g, " ");
|
|
249
|
+
this.streamText = joined.slice(-240);
|
|
250
|
+
if (this.spinnerLabel === undefined) this.setSpinner("thinking");
|
|
251
|
+
this.changed();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
case "tool-start":
|
|
255
|
+
this.setSpinner(`${event.name.replace(/_/g, " ")}…`);
|
|
256
|
+
return;
|
|
257
|
+
case "tool-progress":
|
|
258
|
+
this.setSpinner(
|
|
259
|
+
event.name
|
|
260
|
+
? `${event.name.replace(/_/g, " ")} · ${event.line}`
|
|
261
|
+
: event.line,
|
|
262
|
+
);
|
|
263
|
+
return;
|
|
264
|
+
case "tool-applied": {
|
|
265
|
+
// A media or explain result leaves the revision alone: nothing to undo.
|
|
266
|
+
const unchanged =
|
|
267
|
+
event.baseRevision !== undefined &&
|
|
268
|
+
event.baseRevision === event.resultRevision;
|
|
269
|
+
this.pushCard(event.summary, {
|
|
270
|
+
tone: "success",
|
|
271
|
+
baseRevision: event.baseRevision,
|
|
272
|
+
resultRevision: event.resultRevision,
|
|
273
|
+
trackId: event.trackId,
|
|
274
|
+
hint: unchanged ? undefined : "^z undo",
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
case "tool-rejected": {
|
|
279
|
+
const what = event.summary ?? event.name?.replace(/_/g, " ");
|
|
280
|
+
const why = event.reason ?? event.diagnostic;
|
|
281
|
+
this.pushCard(
|
|
282
|
+
`rejected${what ? ` · ${what}` : ""}${why ? ` · ${why}` : ""}`,
|
|
283
|
+
{ tone: "warning" },
|
|
284
|
+
);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
case "done": {
|
|
288
|
+
if (this.streamText) this.record("agent", this.streamText);
|
|
289
|
+
this.streamText = "";
|
|
290
|
+
const summary =
|
|
291
|
+
event.summary ?? event.text?.split("\n")[0]?.slice(0, 160);
|
|
292
|
+
if (summary) this.pushCard(summary, { tone: "agent" });
|
|
293
|
+
else if (event.applied === 0)
|
|
294
|
+
this.pushCard("agent made no changes", { tone: "info" });
|
|
295
|
+
this.setSpinner(undefined);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
case "error":
|
|
299
|
+
this.streamText = "";
|
|
300
|
+
this.setSpinner(undefined);
|
|
301
|
+
if (event.code === "aborted")
|
|
302
|
+
this.pushCard(
|
|
303
|
+
`cancelled${event.revision === undefined ? "" : ` · kept rev ${event.revision}`}`,
|
|
304
|
+
{ tone: "warning" },
|
|
305
|
+
);
|
|
306
|
+
else
|
|
307
|
+
this.pushError(
|
|
308
|
+
event.code
|
|
309
|
+
? `agent ${event.code}: ${event.message}`
|
|
310
|
+
: event.message,
|
|
311
|
+
);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private record(kind: TranscriptKind, text: string): void {
|
|
317
|
+
this.log = [
|
|
318
|
+
...this.log,
|
|
319
|
+
{ id: this.nextId++, kind, text, atMs: this.clock() },
|
|
320
|
+
].slice(-this.maxTranscript);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private changed(): void {
|
|
324
|
+
this.version += 1;
|
|
325
|
+
for (const listener of this.listeners) listener();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* A command receipt with its outcome carried structurally: `ok: true` is a
|
|
331
|
+
* success, `false` a failure, `"warn"` a no-op or conflict. Handlers return
|
|
332
|
+
* these so the strip never infers failure from prose.
|
|
333
|
+
*/
|
|
334
|
+
export type Receipt = Readonly<{ ok: boolean | "warn"; text: string }>;
|
|
335
|
+
|
|
336
|
+
export const ok = (text: string): Receipt => ({ ok: true, text });
|
|
337
|
+
export const fail = (text: string): Receipt => ({ ok: false, text });
|
|
338
|
+
export const warn = (text: string): Receipt => ({ ok: "warn", text });
|
|
339
|
+
|
|
340
|
+
/** Tone of a structured receipt; strings fall back to `receiptTone`. */
|
|
341
|
+
export function toneOf(receipt: string | Receipt): CardTone {
|
|
342
|
+
if (typeof receipt === "string") return receiptTone(receipt);
|
|
343
|
+
return receipt.ok === true
|
|
344
|
+
? "success"
|
|
345
|
+
: receipt.ok === "warn"
|
|
346
|
+
? "warning"
|
|
347
|
+
: "error";
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Classify a legacy command receipt string (e.g. "undid · rev 41",
|
|
352
|
+
* "tempo error · …") into a card tone. Fallback for handlers that still
|
|
353
|
+
* return plain strings; new handlers return a `Receipt`.
|
|
354
|
+
*/
|
|
355
|
+
export function receiptTone(message: string): CardTone {
|
|
356
|
+
const lower = message.toLowerCase();
|
|
357
|
+
if (
|
|
358
|
+
/\berror\b|unrecognized|failed|invalid|cannot|can't|not found/.test(lower)
|
|
359
|
+
)
|
|
360
|
+
return "error";
|
|
361
|
+
if (/nothing to|conflict|changed; retry|exists|another window/.test(lower))
|
|
362
|
+
return "warning";
|
|
363
|
+
return "success";
|
|
364
|
+
}
|