@oratis/lisa 0.2.0 → 0.3.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/README.md +102 -9
- package/README.zh-CN.md +101 -9
- package/completions/_lisa +1 -0
- package/completions/lisa.bash +1 -1
- package/completions/lisa.fish +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +12 -0
- package/dist/agent.js.map +1 -1
- package/dist/channels/imessage.d.ts +16 -0
- package/dist/channels/imessage.d.ts.map +1 -1
- package/dist/channels/imessage.js +29 -8
- package/dist/channels/imessage.js.map +1 -1
- package/dist/cli.js +23 -0
- package/dist/cli.js.map +1 -1
- package/dist/env.js +6 -1
- package/dist/env.js.map +1 -1
- package/dist/heartbeat/config.d.ts +10 -0
- package/dist/heartbeat/config.d.ts.map +1 -1
- package/dist/heartbeat/config.js +15 -3
- package/dist/heartbeat/config.js.map +1 -1
- package/dist/heartbeat/install.d.ts.map +1 -1
- package/dist/heartbeat/install.js +80 -15
- package/dist/heartbeat/install.js.map +1 -1
- package/dist/heartbeat/runner.d.ts.map +1 -1
- package/dist/heartbeat/runner.js +30 -0
- package/dist/heartbeat/runner.js.map +1 -1
- package/dist/integrations/claude-code/parser.d.ts +56 -0
- package/dist/integrations/claude-code/parser.d.ts.map +1 -0
- package/dist/integrations/claude-code/parser.js +224 -0
- package/dist/integrations/claude-code/parser.js.map +1 -0
- package/dist/integrations/claude-code/watcher.d.ts +127 -0
- package/dist/integrations/claude-code/watcher.d.ts.map +1 -0
- package/dist/integrations/claude-code/watcher.js +361 -0
- package/dist/integrations/claude-code/watcher.js.map +1 -0
- package/dist/llm.d.ts +0 -6
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +0 -10
- package/dist/llm.js.map +1 -1
- package/dist/memory/vector.d.ts +5 -1
- package/dist/memory/vector.d.ts.map +1 -1
- package/dist/memory/vector.js +58 -1
- package/dist/memory/vector.js.map +1 -1
- package/dist/mood-bus.d.ts +11 -2
- package/dist/mood-bus.d.ts.map +1 -1
- package/dist/mood-bus.js +15 -2
- package/dist/mood-bus.js.map +1 -1
- package/dist/sessions/store.d.ts +1 -5
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +0 -11
- package/dist/sessions/store.js.map +1 -1
- package/dist/soul/lock.d.ts +38 -0
- package/dist/soul/lock.d.ts.map +1 -0
- package/dist/soul/lock.js +103 -0
- package/dist/soul/lock.js.map +1 -0
- package/dist/soul/paths.d.ts.map +1 -1
- package/dist/soul/paths.js +12 -6
- package/dist/soul/paths.js.map +1 -1
- package/dist/soul/slug.d.ts +40 -0
- package/dist/soul/slug.d.ts.map +1 -0
- package/dist/soul/slug.js +78 -0
- package/dist/soul/slug.js.map +1 -0
- package/dist/soul/store.d.ts +12 -1
- package/dist/soul/store.d.ts.map +1 -1
- package/dist/soul/store.js +40 -23
- package/dist/soul/store.js.map +1 -1
- package/dist/soul/tools.d.ts.map +1 -1
- package/dist/soul/tools.js +10 -3
- package/dist/soul/tools.js.map +1 -1
- package/dist/tools/task.d.ts +0 -1
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +0 -3
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/web_fetch.d.ts +10 -0
- package/dist/tools/web_fetch.d.ts.map +1 -1
- package/dist/tools/web_fetch.js +48 -16
- package/dist/tools/web_fetch.js.map +1 -1
- package/dist/web/assets/lisa-mascot.png +0 -0
- package/dist/web/island.d.ts +12 -0
- package/dist/web/island.d.ts.map +1 -0
- package/dist/web/island.js +948 -0
- package/dist/web/island.js.map +1 -0
- package/dist/web/lisa-html.d.ts +29 -0
- package/dist/web/lisa-html.d.ts.map +1 -0
- package/dist/web/lisa-html.js +2095 -0
- package/dist/web/lisa-html.js.map +1 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +86 -1601
- package/dist/web/server.js.map +1 -1
- package/package.json +13 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code session state parser — Phase 2 of issue #27.
|
|
3
|
+
*
|
|
4
|
+
* Reads the TAIL of a session jsonl (last 32KB max) to derive whether
|
|
5
|
+
* Claude Code is currently:
|
|
6
|
+
*
|
|
7
|
+
* - "working" — Claude is mid-turn (tool call in flight, or assistant
|
|
8
|
+
* streaming, or user just sent something)
|
|
9
|
+
* - "waiting" — Claude finished (stopReason: end_turn) and is now
|
|
10
|
+
* awaiting the next user message; OR Claude requested a
|
|
11
|
+
* tool that needs permission
|
|
12
|
+
* - "error" — most recent meaningful line has is_error: true,
|
|
13
|
+
* hookErrors > 0, or an explicit error envelope
|
|
14
|
+
* - "unknown" — couldn't decide (file empty, malformed, only meta
|
|
15
|
+
* entries)
|
|
16
|
+
*
|
|
17
|
+
* ── PRIVACY CONTRACT (extends watcher.ts) ──────────────────────────
|
|
18
|
+
*
|
|
19
|
+
* We DO read jsonl in this module — necessary to derive state —
|
|
20
|
+
* but we ONLY ever inspect structural / metadata fields:
|
|
21
|
+
*
|
|
22
|
+
* • type
|
|
23
|
+
* • stopReason
|
|
24
|
+
* • is_error / error
|
|
25
|
+
* • hookErrors / hookCount
|
|
26
|
+
* • subtype
|
|
27
|
+
*
|
|
28
|
+
* We NEVER read, log, transmit, or persist `content`, `text`,
|
|
29
|
+
* `message.content`, prompts, or replies. The parser destructures
|
|
30
|
+
* only the fields above and discards the rest. A reviewer can audit
|
|
31
|
+
* the entire module in 60 seconds — search for `content` or `text`
|
|
32
|
+
* should produce zero hits except in comments like this one.
|
|
33
|
+
*
|
|
34
|
+
* ── ROBUSTNESS ──────────────────────────────────────────────────────
|
|
35
|
+
*
|
|
36
|
+
* Claude Code's jsonl format may change between versions. Unknown
|
|
37
|
+
* shapes return "unknown" rather than throwing. Malformed JSON lines
|
|
38
|
+
* are skipped. Partial writes during streaming are tolerated (we
|
|
39
|
+
* take the LAST complete line). File deletion mid-parse returns
|
|
40
|
+
* "unknown".
|
|
41
|
+
*/
|
|
42
|
+
import fsp from "node:fs/promises";
|
|
43
|
+
const META_TYPES = new Set([
|
|
44
|
+
"ai-title",
|
|
45
|
+
"custom-title",
|
|
46
|
+
"last-prompt",
|
|
47
|
+
"queue-operation",
|
|
48
|
+
"attachment",
|
|
49
|
+
// pr-link entries are written when Claude Code creates a PR. They
|
|
50
|
+
// appear after the actual conversation activity and would otherwise
|
|
51
|
+
// mask the real last-state if we stopped at the bottom-most line.
|
|
52
|
+
"pr-link",
|
|
53
|
+
]);
|
|
54
|
+
const TAIL_BYTES = 32 * 1024;
|
|
55
|
+
export async function parseSessionState(filePath) {
|
|
56
|
+
let size;
|
|
57
|
+
try {
|
|
58
|
+
const st = await fsp.stat(filePath);
|
|
59
|
+
if (!st.isFile())
|
|
60
|
+
return { state: "unknown", reason: "not-a-file" };
|
|
61
|
+
size = st.size;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return { state: "unknown", reason: "stat-failed" };
|
|
65
|
+
}
|
|
66
|
+
if (size === 0)
|
|
67
|
+
return { state: "unknown", reason: "empty" };
|
|
68
|
+
let tail;
|
|
69
|
+
try {
|
|
70
|
+
const fd = await fsp.open(filePath, "r");
|
|
71
|
+
try {
|
|
72
|
+
const length = Math.min(TAIL_BYTES, size);
|
|
73
|
+
const buf = Buffer.alloc(length);
|
|
74
|
+
await fd.read(buf, 0, length, size - length);
|
|
75
|
+
tail = buf.toString("utf8");
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await fd.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { state: "unknown", reason: "read-failed" };
|
|
83
|
+
}
|
|
84
|
+
// Drop the first partial line if we didn't read from the very start —
|
|
85
|
+
// it might be truncated mid-record. (Doesn't matter for our purposes
|
|
86
|
+
// since we walk from the bottom anyway, but cheap insurance.)
|
|
87
|
+
const lines = tail.split("\n").filter(Boolean);
|
|
88
|
+
if (size > TAIL_BYTES && lines.length > 0)
|
|
89
|
+
lines.shift();
|
|
90
|
+
// Walk bottom-up, skip meta entries, decide on the first real one.
|
|
91
|
+
// While walking, also harvest `cwd` (top-level metadata field set
|
|
92
|
+
// by Claude Code at session start) — it's commonly present on every
|
|
93
|
+
// line, so we'll find it quickly.
|
|
94
|
+
let foundCwd;
|
|
95
|
+
let decision = null;
|
|
96
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
97
|
+
const line = lines[i];
|
|
98
|
+
if (!foundCwd) {
|
|
99
|
+
foundCwd = sniffCwd(line);
|
|
100
|
+
}
|
|
101
|
+
if (decision === null) {
|
|
102
|
+
decision = decide(line);
|
|
103
|
+
}
|
|
104
|
+
if (decision !== null && foundCwd)
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (decision !== null) {
|
|
108
|
+
return { ...decision, cwd: foundCwd };
|
|
109
|
+
}
|
|
110
|
+
return { state: "unknown", reason: "only-meta", cwd: foundCwd };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Extract only the top-level `.cwd` string from a line, without
|
|
114
|
+
* parsing the rest. We do a full JSON.parse here for safety (the
|
|
115
|
+
* `cwd` could in theory contain `}` chars), but immediately discard
|
|
116
|
+
* everything except the cwd field. Same privacy rule: structural
|
|
117
|
+
* metadata only, never `content` / `text`.
|
|
118
|
+
*/
|
|
119
|
+
function sniffCwd(line) {
|
|
120
|
+
try {
|
|
121
|
+
const obj = JSON.parse(line);
|
|
122
|
+
if (obj && typeof obj === "object") {
|
|
123
|
+
const cwd = obj.cwd;
|
|
124
|
+
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
125
|
+
return cwd;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch { /* skip */ }
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Parse one jsonl line and decide whether it carries enough info to
|
|
133
|
+
* declare a state. Returns null to mean "keep walking" (meta or
|
|
134
|
+
* unparseable).
|
|
135
|
+
*
|
|
136
|
+
* Destructures only metadata fields. Never touches content / text /
|
|
137
|
+
* message bodies.
|
|
138
|
+
*/
|
|
139
|
+
function decide(line) {
|
|
140
|
+
let entry;
|
|
141
|
+
try {
|
|
142
|
+
entry = JSON.parse(line);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (!entry || typeof entry !== "object")
|
|
148
|
+
return null;
|
|
149
|
+
const e = entry;
|
|
150
|
+
const type = readString(e.type);
|
|
151
|
+
if (!type)
|
|
152
|
+
return null;
|
|
153
|
+
if (META_TYPES.has(type))
|
|
154
|
+
return null;
|
|
155
|
+
// Privacy: from this point on we only ever read metadata fields.
|
|
156
|
+
// Real Claude Code jsonl puts stop_reason nested under .message —
|
|
157
|
+
// we read it via a typed accessor that never touches .message.content.
|
|
158
|
+
const stopReason = readNestedStopReason(e);
|
|
159
|
+
const subtype = readString(e.subtype);
|
|
160
|
+
const isError = e.is_error === true || e.error === true;
|
|
161
|
+
const hookErrors = typeof e.hookErrors === "number" && e.hookErrors > 0;
|
|
162
|
+
if (isError || hookErrors) {
|
|
163
|
+
return { state: "error", reason: "is_error" };
|
|
164
|
+
}
|
|
165
|
+
if (type === "assistant") {
|
|
166
|
+
// `end_turn` = Claude finished talking, waiting for user reply
|
|
167
|
+
// `tool_use` = Claude wants to run a tool. In Claude Code this is
|
|
168
|
+
// usually mid-loop (tool runs, result comes back,
|
|
169
|
+
// Claude continues) — so we report "working" unless
|
|
170
|
+
// the tool requires permission, which surfaces as a
|
|
171
|
+
// separate system entry we'd see later.
|
|
172
|
+
if (stopReason === "end_turn")
|
|
173
|
+
return { state: "waiting", reason: "end_turn" };
|
|
174
|
+
if (stopReason === "tool_use")
|
|
175
|
+
return { state: "working", reason: "tool_use" };
|
|
176
|
+
if (stopReason === "max_tokens")
|
|
177
|
+
return { state: "waiting", reason: "max_tokens" };
|
|
178
|
+
if (stopReason === "stop_sequence")
|
|
179
|
+
return { state: "waiting", reason: "stop_sequence" };
|
|
180
|
+
// Unknown / no stop_reason yet — likely streaming in progress.
|
|
181
|
+
return { state: "working", reason: "assistant" };
|
|
182
|
+
}
|
|
183
|
+
if (type === "user") {
|
|
184
|
+
// The user just typed — Claude is about to (or already) respond.
|
|
185
|
+
return { state: "working", reason: "user" };
|
|
186
|
+
}
|
|
187
|
+
if (type === "tool_use") {
|
|
188
|
+
return { state: "working", reason: "tool_use" };
|
|
189
|
+
}
|
|
190
|
+
if (type === "system") {
|
|
191
|
+
// Permission prompts often come as system entries with a
|
|
192
|
+
// subtype like "permission_request" / "tool_permission". Treat
|
|
193
|
+
// them as "waiting" (user needs to approve).
|
|
194
|
+
if (subtype && /permission/i.test(subtype)) {
|
|
195
|
+
return { state: "waiting", reason: "permission" };
|
|
196
|
+
}
|
|
197
|
+
// Other system entries are transient — keep walking.
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
// Unknown but non-meta type — keep walking; if everything above
|
|
201
|
+
// turns out to be unknown, the caller's loop will fall through to
|
|
202
|
+
// "unknown" anyway.
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
function readString(v) {
|
|
206
|
+
return typeof v === "string" ? v : undefined;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Claude Code stores stop_reason nested under `.message.stop_reason`
|
|
210
|
+
* (snake_case). We extract ONLY that one field; `.message.content`,
|
|
211
|
+
* `.message.text`, etc. are deliberately never touched.
|
|
212
|
+
*/
|
|
213
|
+
function readNestedStopReason(e) {
|
|
214
|
+
const direct = readString(e.stopReason) ?? readString(e.stop_reason);
|
|
215
|
+
if (direct)
|
|
216
|
+
return direct;
|
|
217
|
+
const msg = e.message;
|
|
218
|
+
if (msg && typeof msg === "object") {
|
|
219
|
+
const m = msg;
|
|
220
|
+
return readString(m.stop_reason) ?? readString(m.stopReason);
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/integrations/claude-code/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code session monitor — Phase 1.
|
|
3
|
+
*
|
|
4
|
+
* Watches ~/.claude/projects/<encoded-cwd>/<session-uuid>.jsonl for activity
|
|
5
|
+
* from Claude Code's CLI / IDE clients. Emits an `update` event whenever
|
|
6
|
+
* a session jsonl appears (new) or grows (message). LISA's web server
|
|
7
|
+
* subscribes and broadcasts to /events SSE so the island widget and other
|
|
8
|
+
* surfaces can render a "Claude is busy" indicator.
|
|
9
|
+
*
|
|
10
|
+
* ── PRIVACY CONTRACT ────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* This module NEVER reads the jsonl message contents. Only:
|
|
13
|
+
* - filename / directory name (= encoded cwd)
|
|
14
|
+
* - mtime
|
|
15
|
+
* - size in bytes
|
|
16
|
+
*
|
|
17
|
+
* The user's prompts and Claude's replies stay in their files. We do
|
|
18
|
+
* not parse, log, or transmit them anywhere. Phase 2 might add an
|
|
19
|
+
* opt-in parser to detect "waiting for permission" semantics — that
|
|
20
|
+
* will be a separate, explicit decision.
|
|
21
|
+
*
|
|
22
|
+
* ── DEPENDENCIES ────────────────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* None added. Uses only `node:fs` (`fs.watch` with `recursive: true`
|
|
25
|
+
* works on macOS 10.5+ and Windows 10+ — perfect for our target).
|
|
26
|
+
*
|
|
27
|
+
* ── EVENT SEMANTICS ─────────────────────────────────────────────────
|
|
28
|
+
*
|
|
29
|
+
* - "new" — a session jsonl appeared we hadn't seen before
|
|
30
|
+
* - "message" — an existing session jsonl grew (debounced 200ms so a
|
|
31
|
+
* fast token stream doesn't fire a flood)
|
|
32
|
+
* - (Phase 2) "waiting" / "completed" / "error" — needs jsonl parse
|
|
33
|
+
*
|
|
34
|
+
* Active = mtime within ACTIVE_WINDOW_MS (30 min). listActive()
|
|
35
|
+
* returns sessions sorted newest-first, capped at MAX_LISTED.
|
|
36
|
+
*/
|
|
37
|
+
import { EventEmitter } from "node:events";
|
|
38
|
+
import { type ClaudeSessionState } from "./parser.js";
|
|
39
|
+
/**
|
|
40
|
+
* One row in the in-memory session map. We hold mtime + size only —
|
|
41
|
+
* never any jsonl content.
|
|
42
|
+
*/
|
|
43
|
+
export interface ClaudeSessionInfo {
|
|
44
|
+
/** Encoded form, e.g. "-Users-oratis-Projects-Adex" */
|
|
45
|
+
projectEncoded: string;
|
|
46
|
+
/** Best-effort display label, e.g. "Adex" */
|
|
47
|
+
projectLabel: string;
|
|
48
|
+
/** UUID (.jsonl basename without extension) */
|
|
49
|
+
sessionId: string;
|
|
50
|
+
/** Last modification time (epoch ms) */
|
|
51
|
+
lastMtime: number;
|
|
52
|
+
/** Size in bytes (rough indicator of message volume) */
|
|
53
|
+
size: number;
|
|
54
|
+
/** Derived state — Phase 2; see parser.ts. */
|
|
55
|
+
state: ClaudeSessionState;
|
|
56
|
+
/** Short reason label for the derived state (debugging / tooltip). */
|
|
57
|
+
stateReason: string;
|
|
58
|
+
/**
|
|
59
|
+
* Phase 3.5: cwd from Claude Code's top-level jsonl `.cwd` field.
|
|
60
|
+
* Used by the island UI for "Open in Finder" / "Copy resume
|
|
61
|
+
* command". Undefined if the jsonl didn't record it.
|
|
62
|
+
*/
|
|
63
|
+
cwd?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface ClaudeSessionUpdate {
|
|
66
|
+
event: "new" | "message" | "state_changed";
|
|
67
|
+
projectEncoded: string;
|
|
68
|
+
projectLabel: string;
|
|
69
|
+
sessionId: string;
|
|
70
|
+
state: ClaudeSessionState;
|
|
71
|
+
stateReason: string;
|
|
72
|
+
cwd?: string;
|
|
73
|
+
ts: string;
|
|
74
|
+
}
|
|
75
|
+
type Log = (msg: string) => void;
|
|
76
|
+
export declare class ClaudeCodeWatcher extends EventEmitter {
|
|
77
|
+
private sessions;
|
|
78
|
+
private watcher;
|
|
79
|
+
private retryTimer;
|
|
80
|
+
private repollTimer;
|
|
81
|
+
private pendingChanges;
|
|
82
|
+
private readonly log;
|
|
83
|
+
private started;
|
|
84
|
+
constructor(opts?: {
|
|
85
|
+
log?: Log;
|
|
86
|
+
});
|
|
87
|
+
/** Begin watching. Idempotent. Survives ~/.claude/projects not existing yet. */
|
|
88
|
+
start(): Promise<void>;
|
|
89
|
+
stop(): void;
|
|
90
|
+
/** Sessions modified in the last 30 min, newest first, capped. */
|
|
91
|
+
listActive(): ClaudeSessionInfo[];
|
|
92
|
+
private initialScan;
|
|
93
|
+
private recordExisting;
|
|
94
|
+
private attachWatcher;
|
|
95
|
+
private scheduleRetry;
|
|
96
|
+
/**
|
|
97
|
+
* fs.watch fires the relative path of the changed entry. We debounce
|
|
98
|
+
* to coalesce rapid streaming writes into one `message` event per
|
|
99
|
+
* burst.
|
|
100
|
+
*/
|
|
101
|
+
private handleFsEvent;
|
|
102
|
+
private diff;
|
|
103
|
+
private emitUpdate;
|
|
104
|
+
private makeInfo;
|
|
105
|
+
/**
|
|
106
|
+
* Periodic re-evaluation of active sessions' state. The fs.watch
|
|
107
|
+
* stream only fires when the file changes — but the very condition
|
|
108
|
+
* we want to detect (Claude waiting for permission) is "the file
|
|
109
|
+
* STOPPED changing". So we sweep every few seconds: for each session
|
|
110
|
+
* with a recent mtime, re-stat + re-parse + emit `state_changed` if
|
|
111
|
+
* the derived state has flipped.
|
|
112
|
+
*
|
|
113
|
+
* Cheap: stat is O(1), parser reads only the file's tail.
|
|
114
|
+
*/
|
|
115
|
+
private startRepollLoop;
|
|
116
|
+
private repollActive;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* `~/.claude/projects/-Users-oratis-Projects-Adex` → "Adex".
|
|
120
|
+
* Heuristic: strip a leading dash, replace remaining dashes with slashes,
|
|
121
|
+
* take the basename. Falls back to the raw encoded form if the result
|
|
122
|
+
* looks broken (no `/`, suspiciously empty).
|
|
123
|
+
*/
|
|
124
|
+
export declare function decodeProjectLabel(encoded: string): string;
|
|
125
|
+
export declare const CLAUDE_PROJECTS_DIR: string;
|
|
126
|
+
export {};
|
|
127
|
+
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../../src/integrations/claude-code/watcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAMH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAkCzE;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,6CAA6C;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,KAAK,EAAE,kBAAkB,CAAC;IAC1B,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,KAAK,GAAG,SAAS,GAAG,eAAe,CAAC;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;AAEjC,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,GAAE;QAAE,GAAG,CAAC,EAAE,GAAG,CAAA;KAAO;IAMpC,gFAAgF;IAC1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,IAAI,IAAI,IAAI;IAWZ,kEAAkE;IAClE,UAAU,IAAI,iBAAiB,EAAE;YAUnB,WAAW;YA8BX,cAAc;YAad,aAAa;IA8B3B,OAAO,CAAC,aAAa;IAQrB;;;;OAIG;IACH,OAAO,CAAC,aAAa;YAcP,IAAI;IAiClB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,QAAQ;IAgDhB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;YAUT,YAAY;CAuB3B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,eAAO,MAAM,mBAAmB,QAAe,CAAC"}
|