@dorsk/yubisashi 0.1.0 → 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 +77 -45
- package/dist/picker/index.d.ts +2 -0
- package/dist/picker/index.js +3 -0
- package/dist/picker/index.js.map +1 -0
- package/dist/picker/inspect.d.ts +5 -0
- package/dist/picker/inspect.js +87 -0
- package/dist/picker/inspect.js.map +1 -0
- package/dist/picker/picker.d.ts +29 -0
- package/dist/picker/picker.js +265 -0
- package/dist/picker/picker.js.map +1 -0
- package/dist/picker/protocol.d.ts +79 -0
- package/dist/picker/protocol.js +23 -0
- package/dist/picker/protocol.js.map +1 -0
- package/dist/vite/index.d.ts +15 -0
- package/dist/vite/index.js +150 -0
- package/dist/vite/index.js.map +1 -0
- package/package.json +60 -12
- package/shell/app.css +0 -436
- package/shell/app.js +0 -554
- package/shell/index.html +0 -46
- package/skill/SKILL.md +0 -50
- package/src/cli.ts +0 -259
- package/src/format.ts +0 -43
- package/src/proxy.ts +0 -89
- package/src/server.ts +0 -245
- package/src/store.ts +0 -113
- package/src/transcript.ts +0 -142
package/src/store.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
|
-
|
|
4
|
-
export type Status = 'open' | 'resolved' | 'dismissed';
|
|
5
|
-
|
|
6
|
-
export type Frame = { type: string; file?: string; line?: number; column?: number; name?: string };
|
|
7
|
-
|
|
8
|
-
export type Target = {
|
|
9
|
-
tag: string;
|
|
10
|
-
selector: string;
|
|
11
|
-
text: string;
|
|
12
|
-
html: string;
|
|
13
|
-
attrs: Record<string, string>;
|
|
14
|
-
rect: { x: number; y: number; width: number; height: number };
|
|
15
|
-
source?: { file: string; line: number; column: number };
|
|
16
|
-
stack: Frame[];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type Message = { from: 'user' | 'agent'; text: string; at: string };
|
|
20
|
-
|
|
21
|
-
export type Annotation = {
|
|
22
|
-
id: number;
|
|
23
|
-
createdAt: string;
|
|
24
|
-
status: Status;
|
|
25
|
-
route: string;
|
|
26
|
-
viewport: { width: number; height: number };
|
|
27
|
-
targets: Target[];
|
|
28
|
-
thread: Message[];
|
|
29
|
-
delivered: number;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type NewAnnotation = Pick<Annotation, 'route' | 'viewport' | 'targets'> & {
|
|
33
|
-
comment: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type Listener = (items: Annotation[]) => void;
|
|
37
|
-
|
|
38
|
-
export class Store {
|
|
39
|
-
#items: Annotation[] = [];
|
|
40
|
-
#listeners = new Set<Listener>();
|
|
41
|
-
readonly path: string | undefined;
|
|
42
|
-
|
|
43
|
-
constructor(path?: string) {
|
|
44
|
-
this.path = path;
|
|
45
|
-
if (path) {
|
|
46
|
-
try {
|
|
47
|
-
this.#items = JSON.parse(readFileSync(path, 'utf8'));
|
|
48
|
-
} catch {
|
|
49
|
-
this.#items = [];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
all(): Annotation[] {
|
|
55
|
-
return this.#items;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get(id: number): Annotation | undefined {
|
|
59
|
-
return this.#items.find((a) => a.id === id);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
add(input: NewAnnotation): Annotation {
|
|
63
|
-
const now = new Date().toISOString();
|
|
64
|
-
const annotation: Annotation = {
|
|
65
|
-
id: (this.#items.at(-1)?.id ?? 0) + 1,
|
|
66
|
-
createdAt: now,
|
|
67
|
-
status: 'open',
|
|
68
|
-
route: input.route,
|
|
69
|
-
viewport: input.viewport,
|
|
70
|
-
targets: input.targets,
|
|
71
|
-
thread: [{ from: 'user', text: input.comment, at: now }],
|
|
72
|
-
delivered: 0,
|
|
73
|
-
};
|
|
74
|
-
this.#items.push(annotation);
|
|
75
|
-
this.#changed();
|
|
76
|
-
return annotation;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
post(id: number, from: Message['from'], text: string, status?: Status): Annotation {
|
|
80
|
-
const annotation = this.get(id);
|
|
81
|
-
if (!annotation) throw new Error(`no comment #${id}`);
|
|
82
|
-
if (text) annotation.thread.push({ from, text, at: new Date().toISOString() });
|
|
83
|
-
if (from === 'agent') annotation.delivered = annotation.thread.length;
|
|
84
|
-
if (status) annotation.status = status;
|
|
85
|
-
else if (from === 'user') annotation.status = 'open';
|
|
86
|
-
this.#changed();
|
|
87
|
-
return annotation;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Annotations holding user messages the agent has not received yet. */
|
|
91
|
-
undelivered(): Annotation[] {
|
|
92
|
-
return this.#items.filter((a) => a.thread.length > a.delivered);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
markDelivered(items: Annotation[]): void {
|
|
96
|
-
for (const a of items) a.delivered = a.thread.length;
|
|
97
|
-
if (items.length) this.#changed();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
subscribe(listener: Listener): () => void {
|
|
101
|
-
this.#listeners.add(listener);
|
|
102
|
-
return () => this.#listeners.delete(listener);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
#changed(): void {
|
|
106
|
-
if (this.path) {
|
|
107
|
-
mkdirSync(dirname(this.path), { recursive: true });
|
|
108
|
-
writeFileSync(`${this.path}.tmp`, JSON.stringify(this.#items, null, '\t'));
|
|
109
|
-
renameSync(`${this.path}.tmp`, this.path);
|
|
110
|
-
}
|
|
111
|
-
for (const listener of this.#listeners) listener(this.#items);
|
|
112
|
-
}
|
|
113
|
-
}
|
package/src/transcript.ts
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { closeSync, existsSync, openSync, readdirSync, readSync, statSync, watch } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { StringDecoder } from 'node:string_decoder';
|
|
5
|
-
|
|
6
|
-
export type Entry = {
|
|
7
|
-
id: string;
|
|
8
|
-
at: string;
|
|
9
|
-
kind: 'user' | 'assistant' | 'tool' | 'event';
|
|
10
|
-
text: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export function findTranscript(sessionId: string): string | undefined {
|
|
14
|
-
const root = join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'), 'projects');
|
|
15
|
-
if (!existsSync(root)) return undefined;
|
|
16
|
-
for (const dir of readdirSync(root)) {
|
|
17
|
-
const file = join(root, dir, `${sessionId}.jsonl`);
|
|
18
|
-
if (existsSync(file)) return file;
|
|
19
|
-
}
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const stripWrappers = (text: string) =>
|
|
24
|
-
text.replace(/<([a-z][\w-]*)(\s[^>]*)?>[\s\S]*?<\/\1>/g, '').trim();
|
|
25
|
-
|
|
26
|
-
function summarizeTool(name: string, input: Record<string, unknown>): string {
|
|
27
|
-
const pick = ['description', 'command', 'file_path', 'pattern', 'url', 'query', 'prompt'];
|
|
28
|
-
for (const key of pick) {
|
|
29
|
-
const value = input[key];
|
|
30
|
-
if (typeof value === 'string' && value)
|
|
31
|
-
return `${name}: ${value.split('\n')[0]?.slice(0, 140)}`;
|
|
32
|
-
}
|
|
33
|
-
return name;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type Block = { type: string; text?: string; name?: string; input?: Record<string, unknown> };
|
|
37
|
-
type Line = {
|
|
38
|
-
type?: string;
|
|
39
|
-
uuid?: string;
|
|
40
|
-
timestamp?: string;
|
|
41
|
-
isSidechain?: boolean;
|
|
42
|
-
message?: { content?: string | Block[] };
|
|
43
|
-
attachment?: { type?: string; prompt?: string };
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export function parseLine(raw: string): Entry[] {
|
|
47
|
-
let line: Line;
|
|
48
|
-
try {
|
|
49
|
-
line = JSON.parse(raw);
|
|
50
|
-
} catch {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
if (line.isSidechain || !line.uuid) return [];
|
|
54
|
-
const base = { at: line.timestamp ?? '' };
|
|
55
|
-
const out: Entry[] = [];
|
|
56
|
-
const push = (kind: Entry['kind'], text: string) => {
|
|
57
|
-
if (text) out.push({ ...base, id: `${line.uuid}:${out.length}`, kind, text });
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
if (line.type === 'attachment' && line.attachment?.type === 'queued_command') {
|
|
61
|
-
const prompt = line.attachment.prompt ?? '';
|
|
62
|
-
const summary = prompt.match(/<summary>([\s\S]*?)<\/summary>/)?.[1];
|
|
63
|
-
if (prompt.includes('<task-notification>'))
|
|
64
|
-
push('event', summary ?? 'background task finished');
|
|
65
|
-
else push('user', stripWrappers(prompt));
|
|
66
|
-
return out;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const content = line.message?.content;
|
|
70
|
-
if (line.type === 'user') {
|
|
71
|
-
if (typeof content === 'string') push('user', stripWrappers(content));
|
|
72
|
-
else
|
|
73
|
-
for (const b of content ?? [])
|
|
74
|
-
if (b.type === 'text') push('user', stripWrappers(b.text ?? ''));
|
|
75
|
-
} else if (line.type === 'assistant' && Array.isArray(content)) {
|
|
76
|
-
for (const b of content) {
|
|
77
|
-
if (b.type === 'text') push('assistant', (b.text ?? '').trim());
|
|
78
|
-
else if (b.type === 'tool_use') push('tool', summarizeTool(b.name ?? 'tool', b.input ?? {}));
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return out;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** Follows a session transcript, replaying the last `backlog` entries first. */
|
|
85
|
-
export class Transcript {
|
|
86
|
-
#entries: Entry[] = [];
|
|
87
|
-
#listeners = new Set<(entries: Entry[]) => void>();
|
|
88
|
-
#offset = 0;
|
|
89
|
-
#partial = '';
|
|
90
|
-
#decoder = new StringDecoder('utf8');
|
|
91
|
-
#timer: NodeJS.Timeout | undefined;
|
|
92
|
-
#watcher: ReturnType<typeof watch> | undefined;
|
|
93
|
-
readonly sessionId: string;
|
|
94
|
-
file: string | undefined;
|
|
95
|
-
|
|
96
|
-
constructor(sessionId: string, backlog = 300) {
|
|
97
|
-
this.sessionId = sessionId;
|
|
98
|
-
this.#timer = setInterval(() => this.#poll(backlog), 1000);
|
|
99
|
-
this.#poll(backlog);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
entries(): Entry[] {
|
|
103
|
-
return this.#entries;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
subscribe(listener: (entries: Entry[]) => void): () => void {
|
|
107
|
-
this.#listeners.add(listener);
|
|
108
|
-
return () => this.#listeners.delete(listener);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
close(): void {
|
|
112
|
-
clearInterval(this.#timer);
|
|
113
|
-
this.#watcher?.close();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
#poll(backlog: number): void {
|
|
117
|
-
if (!this.file) {
|
|
118
|
-
this.file = findTranscript(this.sessionId);
|
|
119
|
-
if (!this.file) return;
|
|
120
|
-
this.#watcher = watch(this.file, () => this.#read(backlog));
|
|
121
|
-
}
|
|
122
|
-
this.#read(backlog);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
#read(backlog: number): void {
|
|
126
|
-
if (!this.file) return;
|
|
127
|
-
const size = statSync(this.file).size;
|
|
128
|
-
if (size <= this.#offset) return;
|
|
129
|
-
const fd = openSync(this.file, 'r');
|
|
130
|
-
const buf = Buffer.alloc(size - this.#offset);
|
|
131
|
-
readSync(fd, buf, 0, buf.length, this.#offset);
|
|
132
|
-
closeSync(fd);
|
|
133
|
-
this.#offset = size;
|
|
134
|
-
const lines = (this.#partial + this.#decoder.write(buf)).split('\n');
|
|
135
|
-
this.#partial = lines.pop() ?? '';
|
|
136
|
-
const fresh = lines.flatMap(parseLine);
|
|
137
|
-
if (!fresh.length) return;
|
|
138
|
-
this.#entries.push(...fresh);
|
|
139
|
-
if (this.#entries.length > backlog) this.#entries.splice(0, this.#entries.length - backlog);
|
|
140
|
-
for (const listener of this.#listeners) listener(fresh);
|
|
141
|
-
}
|
|
142
|
-
}
|