@anscribe/core 0.1.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/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/index.d.mts +405 -0
- package/dist/index.mjs +411 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 msmps
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @anscribe/core
|
|
2
|
+
|
|
3
|
+
> Internal capture model shared by `@anscribe/opentui`, `@anscribe/mcp`, and `@anscribe/react`. **Not part of the public API.**
|
|
4
|
+
|
|
5
|
+
You almost certainly want [`@anscribe/opentui`](https://www.npmjs.com/package/@anscribe/opentui).
|
|
6
|
+
|
|
7
|
+
This package exists so the OpenTUI host, the MCP server, and the React enricher can share Schema definitions (`Capture`, `CapturedTarget`, `TerminalCellBounds`, …) and the framework-agnostic Capture Mode state machine without circular dependencies. Its surface is not stabilized; minor releases may break consumers.
|
|
8
|
+
|
|
9
|
+
## License
|
|
10
|
+
|
|
11
|
+
MIT © [msmps](https://github.com/msmps)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { Context, Data, Effect, FileSystem, Layer, Path, Schema } from "effect";
|
|
2
|
+
import * as _$effect_Brand0 from "effect/Brand";
|
|
3
|
+
import * as _$effect_Cause0 from "effect/Cause";
|
|
4
|
+
import * as _$effect_Unify0 from "effect/Unify";
|
|
5
|
+
|
|
6
|
+
//#region src/schema.d.ts
|
|
7
|
+
declare const CaptureId: Schema.brand<Schema.String, "CaptureId">;
|
|
8
|
+
type CaptureId = typeof CaptureId.Type;
|
|
9
|
+
declare const CapturedTargetId: Schema.brand<Schema.String, "CapturedTargetId">;
|
|
10
|
+
type CapturedTargetId = typeof CapturedTargetId.Type;
|
|
11
|
+
declare const generateCaptureId: Effect.Effect<string & _$effect_Brand0.Brand<"CaptureId">, never, never>;
|
|
12
|
+
declare const generateCapturedTargetId: Effect.Effect<string & _$effect_Brand0.Brand<"CapturedTargetId">, never, never>;
|
|
13
|
+
declare function parseIsoTimestampMillis(value: string): number | undefined;
|
|
14
|
+
declare const IsoTimestamp: Schema.brand<Schema.String, "IsoTimestamp">;
|
|
15
|
+
type IsoTimestamp = typeof IsoTimestamp.Type;
|
|
16
|
+
declare const CaptureValidationError_base: Schema.Class<CaptureValidationError, Schema.TaggedStruct<"CaptureValidationError", {
|
|
17
|
+
readonly message: Schema.String;
|
|
18
|
+
readonly cause: Schema.optionalKey<Schema.Unknown>;
|
|
19
|
+
}>, _$effect_Cause0.YieldableError>;
|
|
20
|
+
declare class CaptureValidationError extends CaptureValidationError_base {}
|
|
21
|
+
declare const CapturePersistenceError_base: Schema.Class<CapturePersistenceError, Schema.TaggedStruct<"CapturePersistenceError", {
|
|
22
|
+
readonly message: Schema.String;
|
|
23
|
+
readonly cause: Schema.optionalKey<Schema.Unknown>;
|
|
24
|
+
}>, _$effect_Cause0.YieldableError>;
|
|
25
|
+
declare class CapturePersistenceError extends CapturePersistenceError_base {}
|
|
26
|
+
declare const TerminalCellBounds_base: Schema.Class<TerminalCellBounds, Schema.Struct<{
|
|
27
|
+
readonly x: Schema.Finite;
|
|
28
|
+
readonly y: Schema.Finite;
|
|
29
|
+
readonly width: Schema.Finite;
|
|
30
|
+
readonly height: Schema.Finite;
|
|
31
|
+
}>, {}>;
|
|
32
|
+
declare class TerminalCellBounds extends TerminalCellBounds_base {}
|
|
33
|
+
declare const SourceReference_base: Schema.Class<SourceReference, Schema.Struct<{
|
|
34
|
+
readonly file: Schema.optionalKey<Schema.String>;
|
|
35
|
+
readonly line: Schema.optionalKey<Schema.Finite>;
|
|
36
|
+
readonly column: Schema.optionalKey<Schema.Finite>;
|
|
37
|
+
readonly functionName: Schema.optionalKey<Schema.String>;
|
|
38
|
+
readonly componentName: Schema.optionalKey<Schema.String>;
|
|
39
|
+
readonly origin: Schema.optionalKey<Schema.String>;
|
|
40
|
+
}>, {}>;
|
|
41
|
+
declare class SourceReference extends SourceReference_base {}
|
|
42
|
+
declare const CaptureMetadata_base: Schema.Class<CaptureMetadata, Schema.Struct<{
|
|
43
|
+
readonly identifier: Schema.optionalKey<Schema.String>;
|
|
44
|
+
readonly componentName: Schema.optionalKey<Schema.String>;
|
|
45
|
+
readonly componentPath: Schema.optionalKey<Schema.String>;
|
|
46
|
+
}>, {}>;
|
|
47
|
+
declare class CaptureMetadata extends CaptureMetadata_base {}
|
|
48
|
+
type CaptureStatus = "pending" | "resolved";
|
|
49
|
+
declare const captureStatusSchema: Schema.Literals<readonly ["pending", "resolved"]>;
|
|
50
|
+
declare const CapturedTarget_base: Schema.Class<CapturedTarget, Schema.Struct<{
|
|
51
|
+
readonly id: Schema.brand<Schema.String, "CapturedTargetId">;
|
|
52
|
+
readonly type: Schema.String;
|
|
53
|
+
readonly bounds: typeof TerminalCellBounds;
|
|
54
|
+
readonly ancestry: Schema.$Array<Schema.String>;
|
|
55
|
+
readonly visibleContent: Schema.optionalKey<Schema.String>;
|
|
56
|
+
readonly metadata: Schema.optionalKey<typeof CaptureMetadata>;
|
|
57
|
+
readonly sourceReferences: Schema.optionalKey<Schema.$Array<typeof SourceReference>>;
|
|
58
|
+
}>, {}>;
|
|
59
|
+
declare class CapturedTarget extends CapturedTarget_base {}
|
|
60
|
+
declare const Capture_base: Schema.Class<Capture, Schema.Struct<{
|
|
61
|
+
readonly id: Schema.brand<Schema.String, "CaptureId">;
|
|
62
|
+
readonly status: Schema.Literals<readonly ["pending", "resolved"]>;
|
|
63
|
+
readonly createdAt: Schema.brand<Schema.String, "IsoTimestamp">;
|
|
64
|
+
readonly instruction: Schema.optionalKey<Schema.String>;
|
|
65
|
+
readonly targets: Schema.$Array<typeof CapturedTarget>;
|
|
66
|
+
}>, {}>;
|
|
67
|
+
declare class Capture extends Capture_base {}
|
|
68
|
+
type CaptureInput = ConstructorParameters<typeof Capture>[0];
|
|
69
|
+
declare const makeCapture: (input: CaptureInput) => Effect.Effect<Capture, CaptureValidationError, never>;
|
|
70
|
+
declare function decodeAnscribeData<T>(schema: Schema.Schema<T>, input: unknown): T;
|
|
71
|
+
declare const decodeAnscribeDataEffect: <S extends Schema.Top>(schema: S, input: unknown) => Effect.Effect<S["Type"], CaptureValidationError, S["DecodingServices"]>;
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/project.d.ts
|
|
74
|
+
declare const CaptureProjectResolutionError_base: Schema.Class<CaptureProjectResolutionError, Schema.TaggedStruct<"CaptureProjectResolutionError", {
|
|
75
|
+
readonly message: Schema.String;
|
|
76
|
+
readonly cause: Schema.optionalKey<Schema.Unknown>;
|
|
77
|
+
}>, _$effect_Cause0.YieldableError>;
|
|
78
|
+
declare class CaptureProjectResolutionError extends CaptureProjectResolutionError_base {}
|
|
79
|
+
interface ResolvedCaptureProject {
|
|
80
|
+
readonly projectRoot: string;
|
|
81
|
+
}
|
|
82
|
+
declare const resolveCaptureProjectBoundary: (cwd: string) => Effect.Effect<ResolvedCaptureProject, CaptureProjectResolutionError, FileSystem.FileSystem | Path.Path>;
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/enrichment.d.ts
|
|
85
|
+
interface CaptureEnrichmentOutput {
|
|
86
|
+
readonly metadata?: CaptureMetadata;
|
|
87
|
+
readonly sourceReferences?: readonly SourceReference[];
|
|
88
|
+
}
|
|
89
|
+
declare const noCaptureEnrichment: Effect.Effect<CaptureEnrichmentOutput | undefined>;
|
|
90
|
+
type CaptureMetadataEnricher = (input: {
|
|
91
|
+
renderable: unknown;
|
|
92
|
+
target: CapturedTarget;
|
|
93
|
+
}) => Effect.Effect<CaptureEnrichmentOutput | undefined>;
|
|
94
|
+
interface CaptureMetadataEnrichmentShape {
|
|
95
|
+
readonly enrich: CaptureMetadataEnricher;
|
|
96
|
+
}
|
|
97
|
+
declare const CaptureMetadataEnrichment_base: Context.ServiceClass<CaptureMetadataEnrichment, "anscribe/enrichment/CaptureMetadataEnrichment", CaptureMetadataEnrichmentShape>;
|
|
98
|
+
declare class CaptureMetadataEnrichment extends CaptureMetadataEnrichment_base {
|
|
99
|
+
static readonly live: Layer.Layer<CaptureMetadataEnrichment, never, never>;
|
|
100
|
+
static readonly layer: (enricher: CaptureMetadataEnricher) => Layer.Layer<CaptureMetadataEnrichment, never, never>;
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/picker/state.d.ts
|
|
104
|
+
interface PickerState {
|
|
105
|
+
readonly active: boolean;
|
|
106
|
+
readonly targets: readonly CapturedTarget[];
|
|
107
|
+
readonly currentIndex: number;
|
|
108
|
+
readonly selectedTargetIds: readonly CapturedTargetId[];
|
|
109
|
+
}
|
|
110
|
+
declare function selectCurrentTarget(state: PickerState): CapturedTarget | undefined;
|
|
111
|
+
declare function resolveTargetAtCell(targets: readonly CapturedTarget[], x: number, y: number): CapturedTarget | undefined;
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/capture-mode/state.d.ts
|
|
114
|
+
interface CaptureModeState extends PickerState {
|
|
115
|
+
readonly instructionDraft: boolean;
|
|
116
|
+
}
|
|
117
|
+
declare const initialState: CaptureModeState;
|
|
118
|
+
type CaptureModeIntent = Data.TaggedEnum<{
|
|
119
|
+
EnterMode: {
|
|
120
|
+
readonly targets: readonly CapturedTarget[];
|
|
121
|
+
};
|
|
122
|
+
ExitMode: {};
|
|
123
|
+
MoveSelection: {
|
|
124
|
+
readonly direction: "next" | "previous";
|
|
125
|
+
};
|
|
126
|
+
DeselectCurrent: {};
|
|
127
|
+
ToggleCurrent: {};
|
|
128
|
+
SelectAtCell: {
|
|
129
|
+
readonly x: number;
|
|
130
|
+
readonly y: number;
|
|
131
|
+
};
|
|
132
|
+
StartDraft: {};
|
|
133
|
+
CommitDraft: {
|
|
134
|
+
readonly body: string;
|
|
135
|
+
};
|
|
136
|
+
CancelDraft: {};
|
|
137
|
+
}>;
|
|
138
|
+
declare const CaptureModeIntent: {
|
|
139
|
+
readonly EnterMode: Data.TaggedEnum.ConstructorFrom<{
|
|
140
|
+
readonly _tag: "EnterMode";
|
|
141
|
+
readonly targets: readonly CapturedTarget[];
|
|
142
|
+
}, "_tag">;
|
|
143
|
+
readonly ExitMode: Data.TaggedEnum.ConstructorFrom<{
|
|
144
|
+
readonly _tag: "ExitMode";
|
|
145
|
+
}, "_tag">;
|
|
146
|
+
readonly MoveSelection: Data.TaggedEnum.ConstructorFrom<{
|
|
147
|
+
readonly _tag: "MoveSelection";
|
|
148
|
+
readonly direction: "next" | "previous";
|
|
149
|
+
}, "_tag">;
|
|
150
|
+
readonly DeselectCurrent: Data.TaggedEnum.ConstructorFrom<{
|
|
151
|
+
readonly _tag: "DeselectCurrent";
|
|
152
|
+
}, "_tag">;
|
|
153
|
+
readonly ToggleCurrent: Data.TaggedEnum.ConstructorFrom<{
|
|
154
|
+
readonly _tag: "ToggleCurrent";
|
|
155
|
+
}, "_tag">;
|
|
156
|
+
readonly SelectAtCell: Data.TaggedEnum.ConstructorFrom<{
|
|
157
|
+
readonly _tag: "SelectAtCell";
|
|
158
|
+
readonly x: number;
|
|
159
|
+
readonly y: number;
|
|
160
|
+
}, "_tag">;
|
|
161
|
+
readonly StartDraft: Data.TaggedEnum.ConstructorFrom<{
|
|
162
|
+
readonly _tag: "StartDraft";
|
|
163
|
+
}, "_tag">;
|
|
164
|
+
readonly CommitDraft: Data.TaggedEnum.ConstructorFrom<{
|
|
165
|
+
readonly _tag: "CommitDraft";
|
|
166
|
+
readonly body: string;
|
|
167
|
+
}, "_tag">;
|
|
168
|
+
readonly CancelDraft: Data.TaggedEnum.ConstructorFrom<{
|
|
169
|
+
readonly _tag: "CancelDraft";
|
|
170
|
+
}, "_tag">;
|
|
171
|
+
readonly $is: <Tag extends "EnterMode" | "ExitMode" | "MoveSelection" | "DeselectCurrent" | "ToggleCurrent" | "SelectAtCell" | "StartDraft" | "CommitDraft" | "CancelDraft">(tag: Tag) => (u: unknown) => u is Extract<{
|
|
172
|
+
readonly _tag: "EnterMode";
|
|
173
|
+
readonly targets: readonly CapturedTarget[];
|
|
174
|
+
}, {
|
|
175
|
+
readonly _tag: Tag;
|
|
176
|
+
}> | Extract<{
|
|
177
|
+
readonly _tag: "ExitMode";
|
|
178
|
+
}, {
|
|
179
|
+
readonly _tag: Tag;
|
|
180
|
+
}> | Extract<{
|
|
181
|
+
readonly _tag: "MoveSelection";
|
|
182
|
+
readonly direction: "next" | "previous";
|
|
183
|
+
}, {
|
|
184
|
+
readonly _tag: Tag;
|
|
185
|
+
}> | Extract<{
|
|
186
|
+
readonly _tag: "DeselectCurrent";
|
|
187
|
+
}, {
|
|
188
|
+
readonly _tag: Tag;
|
|
189
|
+
}> | Extract<{
|
|
190
|
+
readonly _tag: "ToggleCurrent";
|
|
191
|
+
}, {
|
|
192
|
+
readonly _tag: Tag;
|
|
193
|
+
}> | Extract<{
|
|
194
|
+
readonly _tag: "SelectAtCell";
|
|
195
|
+
readonly x: number;
|
|
196
|
+
readonly y: number;
|
|
197
|
+
}, {
|
|
198
|
+
readonly _tag: Tag;
|
|
199
|
+
}> | Extract<{
|
|
200
|
+
readonly _tag: "StartDraft";
|
|
201
|
+
}, {
|
|
202
|
+
readonly _tag: Tag;
|
|
203
|
+
}> | Extract<{
|
|
204
|
+
readonly _tag: "CommitDraft";
|
|
205
|
+
readonly body: string;
|
|
206
|
+
}, {
|
|
207
|
+
readonly _tag: Tag;
|
|
208
|
+
}> | Extract<{
|
|
209
|
+
readonly _tag: "CancelDraft";
|
|
210
|
+
}, {
|
|
211
|
+
readonly _tag: Tag;
|
|
212
|
+
}>;
|
|
213
|
+
readonly $match: {
|
|
214
|
+
<Cases extends {
|
|
215
|
+
readonly EnterMode: (args: {
|
|
216
|
+
readonly _tag: "EnterMode";
|
|
217
|
+
readonly targets: readonly CapturedTarget[];
|
|
218
|
+
}) => any;
|
|
219
|
+
readonly ExitMode: (args: {
|
|
220
|
+
readonly _tag: "ExitMode";
|
|
221
|
+
}) => any;
|
|
222
|
+
readonly MoveSelection: (args: {
|
|
223
|
+
readonly _tag: "MoveSelection";
|
|
224
|
+
readonly direction: "next" | "previous";
|
|
225
|
+
}) => any;
|
|
226
|
+
readonly DeselectCurrent: (args: {
|
|
227
|
+
readonly _tag: "DeselectCurrent";
|
|
228
|
+
}) => any;
|
|
229
|
+
readonly ToggleCurrent: (args: {
|
|
230
|
+
readonly _tag: "ToggleCurrent";
|
|
231
|
+
}) => any;
|
|
232
|
+
readonly SelectAtCell: (args: {
|
|
233
|
+
readonly _tag: "SelectAtCell";
|
|
234
|
+
readonly x: number;
|
|
235
|
+
readonly y: number;
|
|
236
|
+
}) => any;
|
|
237
|
+
readonly StartDraft: (args: {
|
|
238
|
+
readonly _tag: "StartDraft";
|
|
239
|
+
}) => any;
|
|
240
|
+
readonly CommitDraft: (args: {
|
|
241
|
+
readonly _tag: "CommitDraft";
|
|
242
|
+
readonly body: string;
|
|
243
|
+
}) => any;
|
|
244
|
+
readonly CancelDraft: (args: {
|
|
245
|
+
readonly _tag: "CancelDraft";
|
|
246
|
+
}) => any;
|
|
247
|
+
}>(cases: Cases): (value: {
|
|
248
|
+
readonly _tag: "EnterMode";
|
|
249
|
+
readonly targets: readonly CapturedTarget[];
|
|
250
|
+
} | {
|
|
251
|
+
readonly _tag: "ExitMode";
|
|
252
|
+
} | {
|
|
253
|
+
readonly _tag: "MoveSelection";
|
|
254
|
+
readonly direction: "next" | "previous";
|
|
255
|
+
} | {
|
|
256
|
+
readonly _tag: "DeselectCurrent";
|
|
257
|
+
} | {
|
|
258
|
+
readonly _tag: "ToggleCurrent";
|
|
259
|
+
} | {
|
|
260
|
+
readonly _tag: "SelectAtCell";
|
|
261
|
+
readonly x: number;
|
|
262
|
+
readonly y: number;
|
|
263
|
+
} | {
|
|
264
|
+
readonly _tag: "StartDraft";
|
|
265
|
+
} | {
|
|
266
|
+
readonly _tag: "CommitDraft";
|
|
267
|
+
readonly body: string;
|
|
268
|
+
} | {
|
|
269
|
+
readonly _tag: "CancelDraft";
|
|
270
|
+
}) => _$effect_Unify0.Unify<ReturnType<Cases["EnterMode" | "ExitMode" | "MoveSelection" | "DeselectCurrent" | "ToggleCurrent" | "SelectAtCell" | "StartDraft" | "CommitDraft" | "CancelDraft"]>>;
|
|
271
|
+
<Cases extends {
|
|
272
|
+
readonly EnterMode: (args: {
|
|
273
|
+
readonly _tag: "EnterMode";
|
|
274
|
+
readonly targets: readonly CapturedTarget[];
|
|
275
|
+
}) => any;
|
|
276
|
+
readonly ExitMode: (args: {
|
|
277
|
+
readonly _tag: "ExitMode";
|
|
278
|
+
}) => any;
|
|
279
|
+
readonly MoveSelection: (args: {
|
|
280
|
+
readonly _tag: "MoveSelection";
|
|
281
|
+
readonly direction: "next" | "previous";
|
|
282
|
+
}) => any;
|
|
283
|
+
readonly DeselectCurrent: (args: {
|
|
284
|
+
readonly _tag: "DeselectCurrent";
|
|
285
|
+
}) => any;
|
|
286
|
+
readonly ToggleCurrent: (args: {
|
|
287
|
+
readonly _tag: "ToggleCurrent";
|
|
288
|
+
}) => any;
|
|
289
|
+
readonly SelectAtCell: (args: {
|
|
290
|
+
readonly _tag: "SelectAtCell";
|
|
291
|
+
readonly x: number;
|
|
292
|
+
readonly y: number;
|
|
293
|
+
}) => any;
|
|
294
|
+
readonly StartDraft: (args: {
|
|
295
|
+
readonly _tag: "StartDraft";
|
|
296
|
+
}) => any;
|
|
297
|
+
readonly CommitDraft: (args: {
|
|
298
|
+
readonly _tag: "CommitDraft";
|
|
299
|
+
readonly body: string;
|
|
300
|
+
}) => any;
|
|
301
|
+
readonly CancelDraft: (args: {
|
|
302
|
+
readonly _tag: "CancelDraft";
|
|
303
|
+
}) => any;
|
|
304
|
+
}>(value: {
|
|
305
|
+
readonly _tag: "EnterMode";
|
|
306
|
+
readonly targets: readonly CapturedTarget[];
|
|
307
|
+
} | {
|
|
308
|
+
readonly _tag: "ExitMode";
|
|
309
|
+
} | {
|
|
310
|
+
readonly _tag: "MoveSelection";
|
|
311
|
+
readonly direction: "next" | "previous";
|
|
312
|
+
} | {
|
|
313
|
+
readonly _tag: "DeselectCurrent";
|
|
314
|
+
} | {
|
|
315
|
+
readonly _tag: "ToggleCurrent";
|
|
316
|
+
} | {
|
|
317
|
+
readonly _tag: "SelectAtCell";
|
|
318
|
+
readonly x: number;
|
|
319
|
+
readonly y: number;
|
|
320
|
+
} | {
|
|
321
|
+
readonly _tag: "StartDraft";
|
|
322
|
+
} | {
|
|
323
|
+
readonly _tag: "CommitDraft";
|
|
324
|
+
readonly body: string;
|
|
325
|
+
} | {
|
|
326
|
+
readonly _tag: "CancelDraft";
|
|
327
|
+
}, cases: Cases): _$effect_Unify0.Unify<ReturnType<Cases["EnterMode" | "ExitMode" | "MoveSelection" | "DeselectCurrent" | "ToggleCurrent" | "SelectAtCell" | "StartDraft" | "CommitDraft" | "CancelDraft"]>>;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
declare function transition(state: CaptureModeState, intent: CaptureModeIntent): CaptureModeState;
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/capture-mode/service.d.ts
|
|
333
|
+
/**
|
|
334
|
+
* Reported by host adapters when forwarding a `CaptureModeDispatchResult.toPersist`
|
|
335
|
+
* Capture to its sink fails or when dispatch itself rejects the draft as invalid.
|
|
336
|
+
*/
|
|
337
|
+
type CaptureModeDispatchError = CaptureValidationError | CapturePersistenceError;
|
|
338
|
+
interface CaptureModeDispatchResult {
|
|
339
|
+
readonly state: CaptureModeState;
|
|
340
|
+
readonly toPersist?: Capture;
|
|
341
|
+
}
|
|
342
|
+
declare const CapturePersistence_base: Context.ServiceClass<CapturePersistence, "anscribe/capture-mode/service/CapturePersistence", {
|
|
343
|
+
readonly createCapture: (capture: Capture) => Effect.Effect<void, CapturePersistenceError>;
|
|
344
|
+
}>;
|
|
345
|
+
declare class CapturePersistence extends CapturePersistence_base {}
|
|
346
|
+
declare const CaptureHostFailureReporter_base: Context.ServiceClass<CaptureHostFailureReporter, "anscribe/capture-mode/service/CaptureHostFailureReporter", {
|
|
347
|
+
readonly report: (error: CaptureModeDispatchError) => Effect.Effect<void>;
|
|
348
|
+
}>;
|
|
349
|
+
/**
|
|
350
|
+
* Host adapters route `CaptureModeDispatchError` here so the failure surfaces
|
|
351
|
+
* as a structured log entry. The default reporter logs via `Effect.logError`;
|
|
352
|
+
* tests override with `Layer.succeed(CaptureHostFailureReporter, ...)`.
|
|
353
|
+
*/
|
|
354
|
+
declare class CaptureHostFailureReporter extends CaptureHostFailureReporter_base {
|
|
355
|
+
static readonly live: Layer.Layer<CaptureHostFailureReporter, never, never>;
|
|
356
|
+
}
|
|
357
|
+
declare const CaptureMode_base: Context.ServiceClass<CaptureMode, "anscribe/capture-mode/service/CaptureMode", {
|
|
358
|
+
readonly dispatch: (intent: CaptureModeIntent) => Effect.Effect<CaptureModeDispatchResult, CaptureValidationError>;
|
|
359
|
+
readonly current: () => Effect.Effect<CaptureModeState>;
|
|
360
|
+
}>;
|
|
361
|
+
declare class CaptureMode extends CaptureMode_base {
|
|
362
|
+
static readonly live: Layer.Layer<CaptureMode, never, never>;
|
|
363
|
+
}
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/capture-overlay.d.ts
|
|
366
|
+
declare const ANSCRIBE_OVERLAY: unique symbol;
|
|
367
|
+
declare function markAsOverlay<R extends object>(renderable: R): R;
|
|
368
|
+
declare function isAnscribeOverlay(renderable: unknown): boolean;
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region src/clipboard-format.d.ts
|
|
371
|
+
declare function formatCaptureForClipboard(capture: Capture): string;
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/sinks.d.ts
|
|
374
|
+
/**
|
|
375
|
+
* A destination that committed Captures are written to.
|
|
376
|
+
*
|
|
377
|
+
* Host adapters (e.g. `installCapture` in `@anscribe/opentui`) always write
|
|
378
|
+
* every committed Capture to the system clipboard via OSC52 first, then fan
|
|
379
|
+
* out to any user-supplied sinks in order. A sink encapsulates whatever
|
|
380
|
+
* resources its writes need — the host calls `close()` on shutdown so the
|
|
381
|
+
* sink can release them.
|
|
382
|
+
*
|
|
383
|
+
* Sinks are constructed by factories exposed from the package that owns the
|
|
384
|
+
* destination — see `mcpSink()` from `@anscribe/mcp` for the canonical
|
|
385
|
+
* example. Authors of new sinks can implement this interface directly.
|
|
386
|
+
*
|
|
387
|
+
* Sink `write` is allowed to throw or reject; the host wraps the failure in
|
|
388
|
+
* a `CapturePersistenceError` tagged with the sink's `name` and routes it
|
|
389
|
+
* through `CaptureHostFailureReporter`. A failing sink does not roll back
|
|
390
|
+
* earlier sinks' writes or the clipboard handoff.
|
|
391
|
+
*/
|
|
392
|
+
interface CaptureSink {
|
|
393
|
+
/**
|
|
394
|
+
* Identifier surfaced in failure messages (e.g. `"anscribe-mcp"`). Should
|
|
395
|
+
* be stable across instances; the React adapter uses the name set to gate
|
|
396
|
+
* `useEffect` reinitialisation when sink instances change between renders.
|
|
397
|
+
*/
|
|
398
|
+
readonly name: string;
|
|
399
|
+
/** Persist a Capture. Called once per committed Capture, in array order. */
|
|
400
|
+
readonly write: (capture: Capture) => Promise<void>;
|
|
401
|
+
/** Optional cleanup. Called by the host on `dispose()` / `close()`. */
|
|
402
|
+
readonly close?: () => Promise<void>;
|
|
403
|
+
}
|
|
404
|
+
//#endregion
|
|
405
|
+
export { ANSCRIBE_OVERLAY, Capture, CaptureEnrichmentOutput, CaptureHostFailureReporter, CaptureId, CaptureInput, CaptureMetadata, CaptureMetadataEnricher, CaptureMetadataEnrichment, CaptureMode, CaptureModeDispatchError, CaptureModeDispatchResult, CaptureModeIntent, CaptureModeState, CapturePersistence, CapturePersistenceError, CaptureProjectResolutionError, CaptureSink, CaptureStatus, CaptureValidationError, CapturedTarget, CapturedTargetId, IsoTimestamp, type PickerState, ResolvedCaptureProject, SourceReference, TerminalCellBounds, captureStatusSchema, decodeAnscribeData, decodeAnscribeDataEffect, formatCaptureForClipboard, generateCaptureId, generateCapturedTargetId, initialState, isAnscribeOverlay, makeCapture, markAsOverlay, noCaptureEnrichment, parseIsoTimestampMillis, resolveCaptureProjectBoundary, resolveTargetAtCell, selectCurrentTarget, transition };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { Clock, Context, Data, DateTime, Effect, FileSystem, Layer, Path, Schema, SubscriptionRef } from "effect";
|
|
2
|
+
import { customAlphabet } from "nanoid";
|
|
3
|
+
//#region src/schema.ts
|
|
4
|
+
const CaptureId = Schema.String.pipe(Schema.brand("CaptureId"));
|
|
5
|
+
const CapturedTargetId = Schema.String.pipe(Schema.brand("CapturedTargetId"));
|
|
6
|
+
const generateNanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 12);
|
|
7
|
+
const generateCaptureId = Effect.sync(() => CaptureId.make(generateNanoid()));
|
|
8
|
+
const generateCapturedTargetId = Effect.sync(() => CapturedTargetId.make(generateNanoid()));
|
|
9
|
+
const isIsoTimestamp = Schema.makeFilter((value) => {
|
|
10
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) && parseIsoTimestampMillis(value) !== void 0 ? true : "Expected ISO timestamp";
|
|
11
|
+
});
|
|
12
|
+
function parseIsoTimestampMillis(value) {
|
|
13
|
+
const millis = Date.parse(value);
|
|
14
|
+
return Number.isFinite(millis) ? millis : void 0;
|
|
15
|
+
}
|
|
16
|
+
const IsoTimestamp = Schema.String.check(isIsoTimestamp).pipe(Schema.brand("IsoTimestamp"));
|
|
17
|
+
var CaptureValidationError = class extends Schema.TaggedErrorClass()("CaptureValidationError", {
|
|
18
|
+
message: Schema.String,
|
|
19
|
+
cause: Schema.optionalKey(Schema.Unknown)
|
|
20
|
+
}) {};
|
|
21
|
+
var CapturePersistenceError = class extends Schema.TaggedErrorClass()("CapturePersistenceError", {
|
|
22
|
+
message: Schema.String,
|
|
23
|
+
cause: Schema.optionalKey(Schema.Unknown)
|
|
24
|
+
}) {};
|
|
25
|
+
var TerminalCellBounds = class extends Schema.Class("TerminalCellBounds")({
|
|
26
|
+
x: Schema.Finite,
|
|
27
|
+
y: Schema.Finite,
|
|
28
|
+
width: Schema.Finite,
|
|
29
|
+
height: Schema.Finite
|
|
30
|
+
}) {};
|
|
31
|
+
var SourceReference = class extends Schema.Class("SourceReference")({
|
|
32
|
+
file: Schema.optionalKey(Schema.String),
|
|
33
|
+
line: Schema.optionalKey(Schema.Finite),
|
|
34
|
+
column: Schema.optionalKey(Schema.Finite),
|
|
35
|
+
functionName: Schema.optionalKey(Schema.String),
|
|
36
|
+
componentName: Schema.optionalKey(Schema.String),
|
|
37
|
+
origin: Schema.optionalKey(Schema.String)
|
|
38
|
+
}) {};
|
|
39
|
+
var CaptureMetadata = class extends Schema.Class("CaptureMetadata")({
|
|
40
|
+
identifier: Schema.optionalKey(Schema.String),
|
|
41
|
+
componentName: Schema.optionalKey(Schema.String),
|
|
42
|
+
componentPath: Schema.optionalKey(Schema.String)
|
|
43
|
+
}) {};
|
|
44
|
+
const captureStatusSchema = Schema.Literals(["pending", "resolved"]);
|
|
45
|
+
var CapturedTarget = class extends Schema.Class("CapturedTarget")({
|
|
46
|
+
id: CapturedTargetId,
|
|
47
|
+
type: Schema.String,
|
|
48
|
+
bounds: TerminalCellBounds,
|
|
49
|
+
ancestry: Schema.Array(Schema.String),
|
|
50
|
+
visibleContent: Schema.optionalKey(Schema.String),
|
|
51
|
+
metadata: Schema.optionalKey(CaptureMetadata),
|
|
52
|
+
sourceReferences: Schema.optionalKey(Schema.Array(SourceReference))
|
|
53
|
+
}) {};
|
|
54
|
+
var Capture = class extends Schema.Class("Capture")({
|
|
55
|
+
id: CaptureId,
|
|
56
|
+
status: captureStatusSchema,
|
|
57
|
+
createdAt: IsoTimestamp,
|
|
58
|
+
instruction: Schema.optionalKey(Schema.String),
|
|
59
|
+
targets: Schema.Array(CapturedTarget)
|
|
60
|
+
}) {};
|
|
61
|
+
const makeCapture = (input) => decodeAnscribeDataEffect(Capture, input);
|
|
62
|
+
function decodeAnscribeData(schema, input) {
|
|
63
|
+
try {
|
|
64
|
+
return Schema.decodeUnknownSync(schema)(input);
|
|
65
|
+
} catch (cause) {
|
|
66
|
+
throw new CaptureValidationError({
|
|
67
|
+
message: "Invalid Anscribe data",
|
|
68
|
+
cause
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const decodeAnscribeDataEffect = (schema, input) => Schema.decodeUnknownEffect(schema)(input).pipe(Effect.mapError((cause) => new CaptureValidationError({
|
|
73
|
+
message: "Invalid Anscribe data",
|
|
74
|
+
cause
|
|
75
|
+
})));
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/project.ts
|
|
78
|
+
var CaptureProjectResolutionError = class extends Schema.TaggedErrorClass()("CaptureProjectResolutionError", {
|
|
79
|
+
message: Schema.String,
|
|
80
|
+
cause: Schema.optionalKey(Schema.Unknown)
|
|
81
|
+
}) {};
|
|
82
|
+
var ProjectMarkerNotFoundError = class extends Schema.TaggedErrorClass()("ProjectMarkerNotFoundError", {
|
|
83
|
+
cwd: Schema.String,
|
|
84
|
+
start: Schema.String
|
|
85
|
+
}) {};
|
|
86
|
+
var ProjectPathResolutionError = class extends Schema.TaggedErrorClass()("ProjectPathResolutionError", {
|
|
87
|
+
path: Schema.String,
|
|
88
|
+
cause: Schema.Unknown
|
|
89
|
+
}) {};
|
|
90
|
+
const workspaceMarkers = [
|
|
91
|
+
"pnpm-workspace.yaml",
|
|
92
|
+
"bun.lock",
|
|
93
|
+
"package-lock.json",
|
|
94
|
+
"yarn.lock",
|
|
95
|
+
"package.json"
|
|
96
|
+
];
|
|
97
|
+
const projectMarkerNames = [".git", ...workspaceMarkers];
|
|
98
|
+
const resolveCaptureProjectBoundary = (cwd) => resolveCaptureProjectRoot(cwd).pipe(Effect.map((projectRoot) => ({ projectRoot })), Effect.mapError(toCaptureProjectResolutionError));
|
|
99
|
+
const resolveCaptureProjectRoot = Effect.fn("Project.resolveCaptureProjectRoot")(function* (cwd) {
|
|
100
|
+
const fs = yield* FileSystem.FileSystem;
|
|
101
|
+
const start = yield* Effect.mapError(fs.realPath(cwd), (cause) => new ProjectPathResolutionError({
|
|
102
|
+
path: cwd,
|
|
103
|
+
cause
|
|
104
|
+
}));
|
|
105
|
+
const projectRoot = yield* findProjectRoot(start);
|
|
106
|
+
if (projectRoot === void 0) return yield* new ProjectMarkerNotFoundError({
|
|
107
|
+
cwd,
|
|
108
|
+
start
|
|
109
|
+
});
|
|
110
|
+
return projectRoot;
|
|
111
|
+
});
|
|
112
|
+
const findProjectRoot = Effect.fn("Project.findProjectRoot")(function* (start) {
|
|
113
|
+
const path = yield* Path.Path;
|
|
114
|
+
let current = start;
|
|
115
|
+
while (true) {
|
|
116
|
+
if (yield* hasProjectMarker(current)) return current;
|
|
117
|
+
const parent = path.dirname(current);
|
|
118
|
+
if (parent === current) return;
|
|
119
|
+
current = parent;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const hasProjectMarker = Effect.fn("Project.hasProjectMarker")(function* (directory) {
|
|
123
|
+
const path = yield* Path.Path;
|
|
124
|
+
if (yield* markerExists(path.join(directory, ".git"))) return true;
|
|
125
|
+
for (const marker of workspaceMarkers) if (yield* markerExists(path.join(directory, marker))) return true;
|
|
126
|
+
return false;
|
|
127
|
+
});
|
|
128
|
+
const markerExists = Effect.fn("Project.markerExists")(function* (path) {
|
|
129
|
+
const fs = yield* FileSystem.FileSystem;
|
|
130
|
+
return yield* Effect.mapError(fs.exists(path), (cause) => new ProjectPathResolutionError({
|
|
131
|
+
path,
|
|
132
|
+
cause
|
|
133
|
+
}));
|
|
134
|
+
});
|
|
135
|
+
function toCaptureProjectResolutionError(error) {
|
|
136
|
+
if (Schema.is(CaptureProjectResolutionError)(error)) return error;
|
|
137
|
+
if (Schema.is(ProjectMarkerNotFoundError)(error)) return new CaptureProjectResolutionError({
|
|
138
|
+
message: `No Anscribe project markers found from ${error.start}. Add a .git directory/file or package workspace marker (${projectMarkerNames.join(", ")}).`,
|
|
139
|
+
cause: error
|
|
140
|
+
});
|
|
141
|
+
if (Schema.is(ProjectPathResolutionError)(error)) return new CaptureProjectResolutionError({
|
|
142
|
+
message: `Unable to resolve Anscribe project path ${error.path}.`,
|
|
143
|
+
cause: error.cause
|
|
144
|
+
});
|
|
145
|
+
return new CaptureProjectResolutionError({
|
|
146
|
+
message: "Unable to resolve Anscribe project path.",
|
|
147
|
+
cause: error
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/enrichment.ts
|
|
152
|
+
const noCaptureEnrichment = Effect.succeed(void 0);
|
|
153
|
+
var CaptureMetadataEnrichment = class extends Context.Service()("anscribe/enrichment/CaptureMetadataEnrichment") {
|
|
154
|
+
static live = Layer.succeed(this, this.of({ enrich: () => noCaptureEnrichment }));
|
|
155
|
+
static layer = (enricher) => Layer.succeed(this, this.of({ enrich: enricher }));
|
|
156
|
+
};
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/picker/state.ts
|
|
159
|
+
const PickerIntent = Data.taggedEnum();
|
|
160
|
+
const PICKER_INTENT_TAGS = new Set([
|
|
161
|
+
"EnterMode",
|
|
162
|
+
"ExitMode",
|
|
163
|
+
"MoveSelection",
|
|
164
|
+
"DeselectCurrent",
|
|
165
|
+
"ToggleCurrent",
|
|
166
|
+
"SelectAtCell"
|
|
167
|
+
]);
|
|
168
|
+
function isPickerIntent(intent) {
|
|
169
|
+
return PICKER_INTENT_TAGS.has(intent._tag);
|
|
170
|
+
}
|
|
171
|
+
function pickerTransition(state, intent) {
|
|
172
|
+
return PickerIntent.$match(intent, {
|
|
173
|
+
EnterMode: ({ targets }) => ({
|
|
174
|
+
active: true,
|
|
175
|
+
targets,
|
|
176
|
+
currentIndex: -1,
|
|
177
|
+
selectedTargetIds: []
|
|
178
|
+
}),
|
|
179
|
+
ExitMode: () => ({
|
|
180
|
+
active: false,
|
|
181
|
+
targets: state.targets,
|
|
182
|
+
currentIndex: -1,
|
|
183
|
+
selectedTargetIds: []
|
|
184
|
+
}),
|
|
185
|
+
MoveSelection: ({ direction }) => {
|
|
186
|
+
if (!state.active || state.targets.length === 0) return state;
|
|
187
|
+
const length = state.targets.length;
|
|
188
|
+
const nextIndex = computeNextIndex(state.currentIndex, length, direction);
|
|
189
|
+
return {
|
|
190
|
+
...state,
|
|
191
|
+
currentIndex: nextIndex
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
DeselectCurrent: () => {
|
|
195
|
+
const currentTarget = selectCurrentTarget(state);
|
|
196
|
+
if (currentTarget === void 0) return state;
|
|
197
|
+
return {
|
|
198
|
+
...state,
|
|
199
|
+
selectedTargetIds: removeSelection(state.selectedTargetIds, currentTarget.id)
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
ToggleCurrent: () => {
|
|
203
|
+
const currentTarget = selectCurrentTarget(state);
|
|
204
|
+
if (currentTarget === void 0) return state;
|
|
205
|
+
return {
|
|
206
|
+
...state,
|
|
207
|
+
selectedTargetIds: state.selectedTargetIds.includes(currentTarget.id) ? removeSelection(state.selectedTargetIds, currentTarget.id) : addSelection(state.selectedTargetIds, currentTarget.id)
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
SelectAtCell: ({ x, y }) => {
|
|
211
|
+
if (!state.active) return state;
|
|
212
|
+
const target = resolveTargetAtCell(state.targets, x, y);
|
|
213
|
+
if (target === void 0) return state;
|
|
214
|
+
const nextIndex = state.targets.findIndex((candidate) => candidate.id === target.id);
|
|
215
|
+
return {
|
|
216
|
+
...state,
|
|
217
|
+
currentIndex: nextIndex,
|
|
218
|
+
selectedTargetIds: state.selectedTargetIds.includes(target.id) ? removeSelection(state.selectedTargetIds, target.id) : addSelection(state.selectedTargetIds, target.id)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function selectCurrentTarget(state) {
|
|
224
|
+
return state.active && state.currentIndex >= 0 ? state.targets[state.currentIndex] : void 0;
|
|
225
|
+
}
|
|
226
|
+
function selectSelectedTargets(state) {
|
|
227
|
+
if (state.selectedTargetIds.length > 0) return state.selectedTargetIds.map((id) => state.targets.find((target) => target.id === id)).filter((target) => target !== void 0);
|
|
228
|
+
const currentTarget = selectCurrentTarget(state);
|
|
229
|
+
return currentTarget !== void 0 ? [currentTarget] : [];
|
|
230
|
+
}
|
|
231
|
+
function resolveTargetAtCell(targets, x, y) {
|
|
232
|
+
return targets.filter((target) => containsCell(target.bounds, x, y)).toSorted((a, b) => b.ancestry.length - a.ancestry.length)[0];
|
|
233
|
+
}
|
|
234
|
+
function computeNextIndex(currentIndex, length, direction) {
|
|
235
|
+
if (currentIndex < 0) return direction === "next" ? 0 : length - 1;
|
|
236
|
+
return direction === "next" ? (currentIndex + 1) % length : (currentIndex - 1 + length) % length;
|
|
237
|
+
}
|
|
238
|
+
function containsCell(bounds, x, y) {
|
|
239
|
+
return x >= bounds.x && x < bounds.x + bounds.width && y >= bounds.y && y < bounds.y + bounds.height;
|
|
240
|
+
}
|
|
241
|
+
function addSelection(existing, id) {
|
|
242
|
+
return existing.includes(id) ? existing : [...existing, id];
|
|
243
|
+
}
|
|
244
|
+
function removeSelection(existing, id) {
|
|
245
|
+
const next = existing.filter((candidate) => candidate !== id);
|
|
246
|
+
return next.length === existing.length ? existing : next;
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/capture-mode/state.ts
|
|
250
|
+
const initialState = {
|
|
251
|
+
active: false,
|
|
252
|
+
targets: [],
|
|
253
|
+
currentIndex: -1,
|
|
254
|
+
selectedTargetIds: [],
|
|
255
|
+
instructionDraft: false
|
|
256
|
+
};
|
|
257
|
+
const CaptureModeIntent = Data.taggedEnum();
|
|
258
|
+
function transition(state, intent) {
|
|
259
|
+
if (isPickerIntent(intent)) {
|
|
260
|
+
const next = pickerTransition(state, intent);
|
|
261
|
+
if (intent._tag === "EnterMode" || intent._tag === "ExitMode") return {
|
|
262
|
+
...next,
|
|
263
|
+
instructionDraft: false
|
|
264
|
+
};
|
|
265
|
+
return next;
|
|
266
|
+
}
|
|
267
|
+
return draftTransition(state, intent);
|
|
268
|
+
}
|
|
269
|
+
function draftTransition(state, intent) {
|
|
270
|
+
switch (intent._tag) {
|
|
271
|
+
case "StartDraft":
|
|
272
|
+
if (selectCurrentTarget(state) === void 0 && state.selectedTargetIds.length === 0) return state;
|
|
273
|
+
return {
|
|
274
|
+
...state,
|
|
275
|
+
instructionDraft: true
|
|
276
|
+
};
|
|
277
|
+
case "CommitDraft": {
|
|
278
|
+
if (!state.instructionDraft) return state;
|
|
279
|
+
const hasExplicitSelection = state.selectedTargetIds.length > 0;
|
|
280
|
+
const willPersist = intent.body.trim().length > 0 && hasResolvedTargets(state);
|
|
281
|
+
return {
|
|
282
|
+
...state,
|
|
283
|
+
instructionDraft: false,
|
|
284
|
+
selectedTargetIds: willPersist && hasExplicitSelection ? [] : state.selectedTargetIds
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
case "CancelDraft":
|
|
288
|
+
if (!state.instructionDraft) return state;
|
|
289
|
+
return {
|
|
290
|
+
...state,
|
|
291
|
+
instructionDraft: false
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function hasResolvedTargets(state) {
|
|
296
|
+
if (state.selectedTargetIds.length > 0) return true;
|
|
297
|
+
return selectCurrentTarget(state) !== void 0;
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/capture-mode/service.ts
|
|
301
|
+
var CapturePersistence = class extends Context.Service()("anscribe/capture-mode/service/CapturePersistence") {};
|
|
302
|
+
/**
|
|
303
|
+
* Host adapters route `CaptureModeDispatchError` here so the failure surfaces
|
|
304
|
+
* as a structured log entry. The default reporter logs via `Effect.logError`;
|
|
305
|
+
* tests override with `Layer.succeed(CaptureHostFailureReporter, ...)`.
|
|
306
|
+
*/
|
|
307
|
+
var CaptureHostFailureReporter = class extends Context.Service()("anscribe/capture-mode/service/CaptureHostFailureReporter") {
|
|
308
|
+
static live = Layer.succeed(this, this.of({ report: Effect.fn("CaptureHostFailureReporter.report")((error) => Effect.logError("Unable to persist Anscribe Capture", error)) }));
|
|
309
|
+
};
|
|
310
|
+
var CaptureMode = class CaptureMode extends Context.Service()("anscribe/capture-mode/service/CaptureMode") {
|
|
311
|
+
static live = Layer.effect(CaptureMode, Effect.gen(function* () {
|
|
312
|
+
const ref = yield* SubscriptionRef.make(initialState);
|
|
313
|
+
return CaptureMode.of({
|
|
314
|
+
dispatch: Effect.fn("CaptureMode.dispatch")(function* (intent) {
|
|
315
|
+
if (intent._tag === "CommitDraft") {
|
|
316
|
+
const previousState = yield* SubscriptionRef.get(ref);
|
|
317
|
+
const state = yield* SubscriptionRef.updateAndGet(ref, (current) => transition(current, intent));
|
|
318
|
+
const toPersist = yield* makeCommitDraftCapture(previousState, intent.body);
|
|
319
|
+
return toPersist !== void 0 ? {
|
|
320
|
+
state,
|
|
321
|
+
toPersist
|
|
322
|
+
} : { state };
|
|
323
|
+
}
|
|
324
|
+
return { state: yield* SubscriptionRef.updateAndGet(ref, (current) => transition(current, intent)) };
|
|
325
|
+
}),
|
|
326
|
+
current: () => SubscriptionRef.get(ref)
|
|
327
|
+
});
|
|
328
|
+
}));
|
|
329
|
+
};
|
|
330
|
+
const makeCommitDraftCapture = (state, body) => Effect.gen(function* () {
|
|
331
|
+
const trimmedBody = body.trim();
|
|
332
|
+
if (!(!state.instructionDraft || trimmedBody.length > 0)) return;
|
|
333
|
+
const selectedTargets = selectSelectedTargets(state);
|
|
334
|
+
if (selectedTargets.length === 0) return;
|
|
335
|
+
const instruction = trimmedBody.length > 0 ? trimmedBody : void 0;
|
|
336
|
+
const millis = yield* Clock.currentTimeMillis;
|
|
337
|
+
const createdAt = DateTime.formatIso(DateTime.makeUnsafe(millis));
|
|
338
|
+
const targets = yield* Effect.forEach(selectedTargets, (target) => Effect.gen(function* () {
|
|
339
|
+
const id = yield* generateCapturedTargetId;
|
|
340
|
+
return yield* decodeAnscribeDataEffect(CapturedTarget, {
|
|
341
|
+
...target,
|
|
342
|
+
id
|
|
343
|
+
});
|
|
344
|
+
}));
|
|
345
|
+
return yield* makeCapture({
|
|
346
|
+
id: yield* generateCaptureId,
|
|
347
|
+
createdAt: IsoTimestamp.make(createdAt),
|
|
348
|
+
status: "pending",
|
|
349
|
+
...instruction !== void 0 && { instruction },
|
|
350
|
+
targets
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region src/capture-overlay.ts
|
|
355
|
+
const ANSCRIBE_OVERLAY = Symbol.for("anscribe.captureOverlay");
|
|
356
|
+
function markAsOverlay(renderable) {
|
|
357
|
+
Object.defineProperty(renderable, ANSCRIBE_OVERLAY, {
|
|
358
|
+
value: true,
|
|
359
|
+
enumerable: false,
|
|
360
|
+
configurable: false,
|
|
361
|
+
writable: false
|
|
362
|
+
});
|
|
363
|
+
return renderable;
|
|
364
|
+
}
|
|
365
|
+
function isAnscribeOverlay(renderable) {
|
|
366
|
+
return renderable !== null && typeof renderable === "object" && renderable[ANSCRIBE_OVERLAY] === true;
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/clipboard-format.ts
|
|
370
|
+
const VISIBLE_CONTENT_PREVIEW_LIMIT = 200;
|
|
371
|
+
function formatCaptureForClipboard(capture) {
|
|
372
|
+
const blocks = [];
|
|
373
|
+
if (capture.instruction !== void 0 && capture.instruction.length > 0) blocks.push(capture.instruction);
|
|
374
|
+
for (const target of capture.targets) blocks.push(formatTarget(target));
|
|
375
|
+
return blocks.join("\n\n");
|
|
376
|
+
}
|
|
377
|
+
function formatTarget(target) {
|
|
378
|
+
return [formatTargetHeader(target), ...formatTargetAncestry(target)].join("\n");
|
|
379
|
+
}
|
|
380
|
+
function formatTargetHeader(target) {
|
|
381
|
+
const identifier = target.metadata?.identifier;
|
|
382
|
+
const idAttr = identifier !== void 0 ? ` id="${identifier}"` : "";
|
|
383
|
+
const preview = formatVisibleContent(target.visibleContent);
|
|
384
|
+
const suffix = preview !== void 0 ? ` ${preview}` : "";
|
|
385
|
+
return `<${target.type}${idAttr}>${suffix}`;
|
|
386
|
+
}
|
|
387
|
+
function formatVisibleContent(content) {
|
|
388
|
+
if (content === void 0) return void 0;
|
|
389
|
+
const collapsed = content.replace(/\s+/g, " ").trim();
|
|
390
|
+
if (collapsed.length === 0) return void 0;
|
|
391
|
+
return `"${collapsed.length > VISIBLE_CONTENT_PREVIEW_LIMIT ? `${collapsed.slice(0, VISIBLE_CONTENT_PREVIEW_LIMIT - 1)}…` : collapsed}"`;
|
|
392
|
+
}
|
|
393
|
+
function formatTargetAncestry(target) {
|
|
394
|
+
const sourceReferences = target.sourceReferences ?? [];
|
|
395
|
+
if (sourceReferences.length > 0) return sourceReferences.map(formatSourceReferenceLine).filter((line) => line !== void 0);
|
|
396
|
+
const componentPath = target.metadata?.componentPath;
|
|
397
|
+
if (componentPath !== void 0 && componentPath.length > 0) return componentPath.split(" > ").map((name) => name.trim()).filter((name) => name.length > 0).map((name) => ` in ${name}`);
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
function formatSourceReferenceLine(reference) {
|
|
401
|
+
const name = reference.componentName ?? "<anonymous>";
|
|
402
|
+
const location = formatSourceLocation(reference.file, reference.line);
|
|
403
|
+
if (location === void 0 && reference.componentName === void 0) return;
|
|
404
|
+
return location !== void 0 ? ` in ${name} (at ${location})` : ` in ${name}`;
|
|
405
|
+
}
|
|
406
|
+
function formatSourceLocation(file, line) {
|
|
407
|
+
if (file === void 0) return void 0;
|
|
408
|
+
return line !== void 0 ? `${file}:${line}` : file;
|
|
409
|
+
}
|
|
410
|
+
//#endregion
|
|
411
|
+
export { ANSCRIBE_OVERLAY, Capture, CaptureHostFailureReporter, CaptureId, CaptureMetadata, CaptureMetadataEnrichment, CaptureMode, CaptureModeIntent, CapturePersistence, CapturePersistenceError, CaptureProjectResolutionError, CaptureValidationError, CapturedTarget, CapturedTargetId, IsoTimestamp, SourceReference, TerminalCellBounds, captureStatusSchema, decodeAnscribeData, decodeAnscribeDataEffect, formatCaptureForClipboard, generateCaptureId, generateCapturedTargetId, initialState, isAnscribeOverlay, makeCapture, markAsOverlay, noCaptureEnrichment, parseIsoTimestampMillis, resolveCaptureProjectBoundary, resolveTargetAtCell, selectCurrentTarget, transition };
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anscribe/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Anscribe core: OpenTUI-agnostic capture model, project discovery, and clipboard formatting.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"anscribe",
|
|
8
|
+
"capture",
|
|
9
|
+
"opentui",
|
|
10
|
+
"tui"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/msmps/anscribe",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/msmps/anscribe/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "msmps",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/msmps/anscribe.git",
|
|
21
|
+
"directory": "packages/core"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
"./package.json": "./package.json",
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"import": "./dist/index.mjs"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"provenance": true,
|
|
37
|
+
"registry": "https://registry.npmjs.org"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"effect": "^4.0.0-beta.65",
|
|
41
|
+
"nanoid": "^5.1.6"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@effect/platform-node": "^4.0.0-beta.65"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsdown",
|
|
48
|
+
"test": "vitest run"
|
|
49
|
+
}
|
|
50
|
+
}
|