@agimon-ai/doompi-file-edit 0.0.1-alpha.9 → 0.0.1-alpha.91
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 +115 -18
- package/dist/apiContracts.cjs +156 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +151 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/extensions/pi.cjs +336 -1
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.mts +5 -2
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +327 -1
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/server.cjs +649 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +641 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +1505 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/fileEditDependencies-CAnZUtLx.mjs +491 -0
- package/dist/fileEditDependencies-CAnZUtLx.mjs.map +1 -0
- package/dist/fileEditDependencies-C_9BS-aJ.cjs +500 -0
- package/dist/fileEditDependencies-C_9BS-aJ.cjs.map +1 -0
- package/dist/index.cjs +3 -1
- package/dist/index.d.mts +311 -5
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -1
- package/dist/treeManifest-BzCs3h_c.mjs +1042 -0
- package/dist/treeManifest-BzCs3h_c.mjs.map +1 -0
- package/dist/treeManifest-CtKbEtkE.cjs +1157 -0
- package/dist/treeManifest-CtKbEtkE.cjs.map +1 -0
- package/dist/webFiles-Bxs3fiqe.mjs +45 -0
- package/dist/webFiles-Bxs3fiqe.mjs.map +1 -0
- package/dist/webFiles-gkmgR3MY.cjs +70 -0
- package/dist/webFiles-gkmgR3MY.cjs.map +1 -0
- package/package.json +87 -23
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs +0 -1
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs +0 -1
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.cjs +0 -1
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.mjs +0 -1
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.cjs +0 -1
- package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateParam.mjs +0 -1
- package/dist/_virtual/_rolldown/runtime.cjs +0 -1
- package/dist/commandLine.cjs +0 -2
- package/dist/commandLine.cjs.map +0 -1
- package/dist/commandLine.mjs +0 -2
- package/dist/commandLine.mjs.map +0 -1
- package/dist/container/index.cjs +0 -2
- package/dist/container/index.cjs.map +0 -1
- package/dist/container/index.d.cts +0 -7
- package/dist/container/index.d.cts.map +0 -1
- package/dist/container/index.d.mts +0 -7
- package/dist/container/index.d.mts.map +0 -1
- package/dist/container/index.mjs +0 -2
- package/dist/container/index.mjs.map +0 -1
- package/dist/domain.d.cts +0 -32
- package/dist/domain.d.cts.map +0 -1
- package/dist/domain.d.mts +0 -32
- package/dist/domain.d.mts.map +0 -1
- package/dist/extension.cjs +0 -2
- package/dist/extension.cjs.map +0 -1
- package/dist/extension.d.cts +0 -6
- package/dist/extension.d.cts.map +0 -1
- package/dist/extension.d.mts +0 -6
- package/dist/extension.d.mts.map +0 -1
- package/dist/extension.mjs +0 -2
- package/dist/extension.mjs.map +0 -1
- package/dist/extensions/doom.cjs +0 -2
- package/dist/extensions/doom.cjs.map +0 -1
- package/dist/extensions/doom.d.cts +0 -6
- package/dist/extensions/doom.d.cts.map +0 -1
- package/dist/extensions/doom.d.mts +0 -6
- package/dist/extensions/doom.d.mts.map +0 -1
- package/dist/extensions/doom.mjs +0 -2
- package/dist/extensions/doom.mjs.map +0 -1
- package/dist/extensions/pi.d.cts +0 -2
- package/dist/index.d.cts +0 -5
- package/dist/services/EditTracker/EditTracker.cjs +0 -2
- package/dist/services/EditTracker/EditTracker.cjs.map +0 -1
- package/dist/services/EditTracker/EditTracker.mjs +0 -2
- package/dist/services/EditTracker/EditTracker.mjs.map +0 -1
- package/dist/services/EditorConfigService/EditorConfigService.cjs +0 -2
- package/dist/services/EditorConfigService/EditorConfigService.cjs.map +0 -1
- package/dist/services/EditorConfigService/EditorConfigService.mjs +0 -2
- package/dist/services/EditorConfigService/EditorConfigService.mjs.map +0 -1
- package/dist/services/EditorLauncher/EditorLauncher.cjs +0 -2
- package/dist/services/EditorLauncher/EditorLauncher.cjs.map +0 -1
- package/dist/services/EditorLauncher/EditorLauncher.mjs +0 -2
- package/dist/services/EditorLauncher/EditorLauncher.mjs.map +0 -1
- package/dist/services/FileEditPaths/FileEditPaths.cjs +0 -2
- package/dist/services/FileEditPaths/FileEditPaths.cjs.map +0 -1
- package/dist/services/FileEditPaths/FileEditPaths.mjs +0 -2
- package/dist/services/FileEditPaths/FileEditPaths.mjs.map +0 -1
- package/dist/services/FileEditWorkflow/FileEditWorkflow.cjs +0 -2
- package/dist/services/FileEditWorkflow/FileEditWorkflow.cjs.map +0 -1
- package/dist/services/FileEditWorkflow/FileEditWorkflow.mjs +0 -2
- package/dist/services/FileEditWorkflow/FileEditWorkflow.mjs.map +0 -1
- package/dist/services/GitDiffService/GitDiffService.cjs +0 -5
- package/dist/services/GitDiffService/GitDiffService.cjs.map +0 -1
- package/dist/services/GitDiffService/GitDiffService.mjs +0 -5
- package/dist/services/GitDiffService/GitDiffService.mjs.map +0 -1
- package/dist/services/TimelineStore/TimelineStore.cjs +0 -3
- package/dist/services/TimelineStore/TimelineStore.cjs.map +0 -1
- package/dist/services/TimelineStore/TimelineStore.mjs +0 -3
- package/dist/services/TimelineStore/TimelineStore.mjs.map +0 -1
- package/dist/tui/fileEditOverlay.cjs +0 -2
- package/dist/tui/fileEditOverlay.cjs.map +0 -1
- package/dist/tui/fileEditOverlay.mjs +0 -2
- package/dist/tui/fileEditOverlay.mjs.map +0 -1
- package/dist/types.cjs +0 -2
- package/dist/types.cjs.map +0 -1
- package/dist/types.d.cts +0 -13
- package/dist/types.d.cts.map +0 -1
- package/dist/types.d.mts +0 -13
- package/dist/types.d.mts.map +0 -1
- package/dist/types.mjs +0 -2
- package/dist/types.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,38 +1,135 @@
|
|
|
1
1
|
# @agimon-ai/doompi-file-edit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A session-scoped file-change timeline and external-editor launcher for Pi.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Part of the [DoomPi distribution](https://www.npmjs.com/package/@agimon-ai/doompi).
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
The package tracks files changed through session tool calls and captures content for diffs, without
|
|
8
|
+
requiring a Git repository. `edit` and `write` name their file in the call, so the package can capture
|
|
9
|
+
the before and after content within its size and format limits. Bash calls instead trigger a bounded
|
|
10
|
+
tree comparison. See [What this cannot see](#what-this-cannot-see) for gaps in that coverage.
|
|
11
|
+
|
|
12
|
+
> **Alpha:** timeline and UI contracts may change between releases.
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- Node.js 22.19.0 or newer
|
|
17
|
+
- Pi 0.85.0 and Pi TUI 0.85.0
|
|
18
|
+
- Git for diffs against `HEAD`
|
|
10
19
|
|
|
11
20
|
## Install
|
|
12
21
|
|
|
22
|
+
`doompi init` and `dpi init` include File Edit in `default.packages` in `.doom/modes.yaml`.
|
|
23
|
+
Remove it or move it to a named layer to change which major modes load it. Standalone Pi loads the
|
|
24
|
+
same extension:
|
|
25
|
+
|
|
13
26
|
```bash
|
|
14
|
-
|
|
27
|
+
pi install npm:@agimon-ai/doompi-file-edit
|
|
15
28
|
```
|
|
16
29
|
|
|
17
|
-
|
|
30
|
+
| Entry | Purpose |
|
|
31
|
+
| ----------------------------------------------- | -------------------------------------------------------- |
|
|
32
|
+
| `@agimon-ai/doompi-file-edit/extensions/pi` | Timeline and DoomPi Leader shortcut |
|
|
33
|
+
| `@agimon-ai/doompi-file-edit/extensions/server` | Server-owned file API and changed-file channel lifecycle |
|
|
34
|
+
| `@agimon-ai/doompi-file-edit` | Library API |
|
|
35
|
+
|
|
36
|
+
## Open the timeline
|
|
37
|
+
|
|
38
|
+
Use `SPC e f` in DoomPi. Select a file to inspect its diff or open it in the configured editor.
|
|
39
|
+
|
|
40
|
+
TUI diffs compare the working tree with Git `HEAD` and are capped at 200 lines and 256 KiB. The
|
|
41
|
+
overlay and external-editor handoff require an interactive TUI. Headless hosts can consume the
|
|
42
|
+
timeline services but cannot display the panel.
|
|
43
|
+
|
|
44
|
+
## In the cockpit
|
|
18
45
|
|
|
19
|
-
|
|
20
|
-
|
|
46
|
+
The `# files` group in the activity dock lists every file the session changed. Selecting one opens a
|
|
47
|
+
temporary tab holding three views of it:
|
|
21
48
|
|
|
22
|
-
|
|
49
|
+
- **diff**: what the session did to the file, followed by each recorded change on its own, so a file
|
|
50
|
+
edited four times shows four distinct diffs rather than one merged blur.
|
|
51
|
+
- **source**: the file as it stands, editable. Saving writes straight to disk and is refused if the
|
|
52
|
+
file changed since it was opened, so a hand edit cannot silently discard what the agent wrote.
|
|
53
|
+
- **preview**: rendered Markdown, or HTML in a sandboxed frame.
|
|
23
54
|
|
|
24
|
-
|
|
55
|
+
Selecting text in any view raises a comment box. Comments queue on the tab and one **send review**
|
|
56
|
+
builds a single message quoting each anchor, so several comments cost one turn.
|
|
57
|
+
|
|
58
|
+
Diffs here do not use Git. The package captures the file's content before an `edit` or `write` runs
|
|
59
|
+
and keeps it, content-addressed, beside the session timeline; both go when the session ends.
|
|
60
|
+
|
|
61
|
+
### What this cannot see
|
|
62
|
+
|
|
63
|
+
- A file the session changed and then deleted. The dock lists things to open, so a row that can only
|
|
64
|
+
answer "this is gone" is left out. The timeline still holds the change, so a tab already open on
|
|
65
|
+
the file keeps working and says why it has nothing to show.
|
|
66
|
+
- A file changed by a background process that outlives its Bash call. The tree is compared when the
|
|
67
|
+
call ends.
|
|
68
|
+
- A file changed outside any tool. There is no always-on watcher.
|
|
69
|
+
- Content past 1 MiB, and binary files. These are listed as changed, without content.
|
|
70
|
+
- A file found only by comparing the tree has no baseline, so it is listed, readable and editable,
|
|
71
|
+
but never diffed. Rows and versions say so rather than showing an empty diff.
|
|
72
|
+
- The tree walk skips dependency and build directories and is capped on entries and depth, so a very
|
|
73
|
+
deep or very large checkout can under-report.
|
|
74
|
+
|
|
75
|
+
## Configure the editor
|
|
76
|
+
|
|
77
|
+
Set the shared DoomPi configuration in `~/.pi/.doom/config.yaml`:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
editor:
|
|
81
|
+
command: nvim +{line} {file}
|
|
82
|
+
```
|
|
25
83
|
|
|
26
|
-
|
|
84
|
+
For a standalone installation, the package falls back to
|
|
85
|
+
`~/.pi/agent/doom-file-edit/config.yaml`. Resolution order is:
|
|
27
86
|
|
|
28
|
-
|
|
87
|
+
1. DoomPi `editor.command`;
|
|
88
|
+
2. package fallback configuration;
|
|
89
|
+
3. `$VISUAL`;
|
|
90
|
+
4. `$EDITOR`;
|
|
91
|
+
5. `nano {file}` on Unix-like systems.
|
|
29
92
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
93
|
+
Only `{file}` and `{line}` placeholders are expanded. The command is split and spawned directly,
|
|
94
|
+
not passed through a shell, so shell operators and expansions are not interpreted.
|
|
95
|
+
|
|
96
|
+
## Data behavior
|
|
97
|
+
|
|
98
|
+
The timeline contains file paths, and the snapshot store holds copies of the file content the
|
|
99
|
+
session changed. That content can be sensitive. Both live under the Pi agent directory,
|
|
100
|
+
`$PI_CODING_AGENT_DIR` or `~/.pi/agent/doom-file-edit`, never inside the working tree, and should be
|
|
101
|
+
protected with the same access controls as Pi session data.
|
|
102
|
+
|
|
103
|
+
A session removes its own timeline and snapshots when it ends. A session that is killed cannot, so
|
|
104
|
+
each session also sweeps state that no longer belongs to anyone: a timeline and its snapshots are
|
|
105
|
+
removed once nothing has written to them for a week. An older build kept this state in the
|
|
106
|
+
repository's git common directory, and that directory is cleared away under the same rule.
|
|
107
|
+
|
|
108
|
+
The session API reads and writes only files the timeline already records, so a request cannot reach
|
|
109
|
+
a file this session never changed.
|
|
110
|
+
|
|
111
|
+
## Public API
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { createFileEditDependencies, fileEditExtension } from '@agimon-ai/doompi-file-edit';
|
|
115
|
+
import type { FileDiff, FileEditEntry } from '@agimon-ai/doompi-file-edit';
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The root also exports editor-resolution and timeline domain types for host integrations.
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pnpm build
|
|
124
|
+
pnpm typecheck
|
|
125
|
+
pnpm test
|
|
126
|
+
pnpm lint
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Maintained by [Agimon](https://agimon.ai/about).
|
|
35
130
|
|
|
36
131
|
## License
|
|
37
132
|
|
|
38
133
|
MIT
|
|
134
|
+
|
|
135
|
+
The public Pi and server loaders live in `src/extensions`. Controllers declare commands, events, APIs, and tracking activities. Services own filesystem and timeline logic; TUI modules assemble and present the review workflow. Public reusable types and dependency construction are exposed through flat `src/exports` entries. The Pi factory owns its optional UI service and awaits cleanup through `onDispose`.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
let _agimon_ai_doompi_core_apiContracts = require("@agimon-ai/doompi-core/apiContracts");
|
|
6
|
+
let typebox = require("typebox");
|
|
7
|
+
//#region src/schemas/apiContracts.ts
|
|
8
|
+
const S = typebox.Type.String();
|
|
9
|
+
const N = typebox.Type.Number();
|
|
10
|
+
const B = typebox.Type.Boolean();
|
|
11
|
+
const O = typebox.Type.Optional;
|
|
12
|
+
const literals = (values) => typebox.Type.Union(values.map((value) => typebox.Type.Literal(value)));
|
|
13
|
+
const Tool = literals([
|
|
14
|
+
"edit",
|
|
15
|
+
"write",
|
|
16
|
+
"bash",
|
|
17
|
+
"user"
|
|
18
|
+
]);
|
|
19
|
+
const Hunk = typebox.Type.Object({
|
|
20
|
+
start: N,
|
|
21
|
+
rows: typebox.Type.Array(typebox.Type.Object({
|
|
22
|
+
marker: literals([
|
|
23
|
+
"+",
|
|
24
|
+
"-",
|
|
25
|
+
" "
|
|
26
|
+
]),
|
|
27
|
+
line: N,
|
|
28
|
+
content: S
|
|
29
|
+
}))
|
|
30
|
+
});
|
|
31
|
+
const Diff = {
|
|
32
|
+
additions: N,
|
|
33
|
+
removals: N,
|
|
34
|
+
hunks: O(typebox.Type.Array(Hunk)),
|
|
35
|
+
note: O(S)
|
|
36
|
+
};
|
|
37
|
+
const Working = typebox.Type.Object({
|
|
38
|
+
content: S,
|
|
39
|
+
hash: S,
|
|
40
|
+
unavailable: B,
|
|
41
|
+
reason: O(S)
|
|
42
|
+
});
|
|
43
|
+
const FileDetailSchema = typebox.Type.Object({
|
|
44
|
+
path: S,
|
|
45
|
+
relPath: S,
|
|
46
|
+
versions: typebox.Type.Array(typebox.Type.Object({
|
|
47
|
+
index: N,
|
|
48
|
+
tool: Tool,
|
|
49
|
+
at: N,
|
|
50
|
+
origin: literals(["tool", "scan"]),
|
|
51
|
+
...Diff
|
|
52
|
+
})),
|
|
53
|
+
cumulative: typebox.Type.Object(Diff),
|
|
54
|
+
working: Working
|
|
55
|
+
});
|
|
56
|
+
const FilesPayloadSchema = typebox.Type.Object({ items: typebox.Type.Array(typebox.Type.Object({
|
|
57
|
+
path: S,
|
|
58
|
+
relPath: S,
|
|
59
|
+
tool: Tool,
|
|
60
|
+
at: N,
|
|
61
|
+
count: N,
|
|
62
|
+
diffable: B
|
|
63
|
+
})) });
|
|
64
|
+
const query = [{
|
|
65
|
+
name: "path",
|
|
66
|
+
in: "query",
|
|
67
|
+
required: true,
|
|
68
|
+
schema: typebox.Type.String({ minLength: 1 })
|
|
69
|
+
}];
|
|
70
|
+
const apiContracts = (0, _agimon_ai_doompi_core_apiContracts.defineApiContract)({
|
|
71
|
+
version: 1,
|
|
72
|
+
dynamic: [],
|
|
73
|
+
http: [
|
|
74
|
+
{
|
|
75
|
+
id: "files.detail",
|
|
76
|
+
scope: "session",
|
|
77
|
+
basePath: "file-edits",
|
|
78
|
+
path: "/detail",
|
|
79
|
+
method: "GET",
|
|
80
|
+
authentication: "owner",
|
|
81
|
+
description: "Read recorded changes and current file content.",
|
|
82
|
+
parameters: query,
|
|
83
|
+
responses: (0, _agimon_ai_doompi_core_apiContracts.jsonApiResponses)(FileDetailSchema)
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "files.preview",
|
|
87
|
+
scope: "session",
|
|
88
|
+
basePath: "file-edits",
|
|
89
|
+
path: "/preview",
|
|
90
|
+
method: "GET",
|
|
91
|
+
authentication: "owner",
|
|
92
|
+
description: "Read a file contained in the session directory.",
|
|
93
|
+
parameters: query,
|
|
94
|
+
responses: (0, _agimon_ai_doompi_core_apiContracts.jsonApiResponses)(typebox.Type.Object({
|
|
95
|
+
path: S,
|
|
96
|
+
relPath: S,
|
|
97
|
+
working: Working
|
|
98
|
+
}))
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "files.save",
|
|
102
|
+
scope: "session",
|
|
103
|
+
basePath: "file-edits",
|
|
104
|
+
path: "/content",
|
|
105
|
+
method: "PUT",
|
|
106
|
+
authentication: "owner",
|
|
107
|
+
description: "Save a recorded file if its hash still matches.",
|
|
108
|
+
body: {
|
|
109
|
+
required: true,
|
|
110
|
+
contentType: "application/json",
|
|
111
|
+
schema: typebox.Type.Object({
|
|
112
|
+
path: S,
|
|
113
|
+
expectedHash: S,
|
|
114
|
+
content: S
|
|
115
|
+
})
|
|
116
|
+
},
|
|
117
|
+
responses: (0, _agimon_ai_doompi_core_apiContracts.jsonApiResponses)(typebox.Type.Object({ hash: S }))
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "files.delete",
|
|
121
|
+
scope: "session",
|
|
122
|
+
basePath: "file-edits",
|
|
123
|
+
path: "/content",
|
|
124
|
+
method: "DELETE",
|
|
125
|
+
authentication: "owner",
|
|
126
|
+
description: "Delete a recorded file.",
|
|
127
|
+
parameters: query,
|
|
128
|
+
responses: {
|
|
129
|
+
"204": { description: "File deleted or already absent." },
|
|
130
|
+
"404": {
|
|
131
|
+
description: "Unrecorded file.",
|
|
132
|
+
schema: typebox.Type.Object({ error: S })
|
|
133
|
+
},
|
|
134
|
+
"409": {
|
|
135
|
+
description: "Deletion failed.",
|
|
136
|
+
schema: typebox.Type.Object({ error: S })
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
sockets: ["global", "workspace"].map((scope) => ({
|
|
142
|
+
id: "files.channel",
|
|
143
|
+
scope,
|
|
144
|
+
service: "doompi.hub.v1",
|
|
145
|
+
member: "file_edits",
|
|
146
|
+
kind: "channel",
|
|
147
|
+
direction: "server-to-client",
|
|
148
|
+
description: "File edits channel payload in the hub frame payload property.",
|
|
149
|
+
input: FilesPayloadSchema
|
|
150
|
+
}))
|
|
151
|
+
});
|
|
152
|
+
//#endregion
|
|
153
|
+
exports.apiContracts = apiContracts;
|
|
154
|
+
exports.default = apiContracts;
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=apiContracts.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiContracts.cjs","names":["Type","defineApiContract","jsonApiResponses"],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/doompi-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Tool = literals(['edit', 'write', 'bash', 'user']);\nconst Hunk = Type.Object({\n start: N,\n rows: Type.Array(Type.Object({ marker: literals(['+', '-', ' ']), line: N, content: S })),\n});\nconst Diff = { additions: N, removals: N, hunks: O(Type.Array(Hunk)), note: O(S) };\nconst Working = Type.Object({ content: S, hash: S, unavailable: B, reason: O(S) });\nexport const FileDetailSchema = Type.Object({\n path: S,\n relPath: S,\n versions: Type.Array(Type.Object({ index: N, tool: Tool, at: N, origin: literals(['tool', 'scan']), ...Diff })),\n cumulative: Type.Object(Diff),\n working: Working,\n});\nexport const FilesPayloadSchema = Type.Object({\n items: Type.Array(Type.Object({ path: S, relPath: S, tool: Tool, at: N, count: N, diffable: B })),\n});\nconst query = [{ name: 'path', in: 'query' as const, required: true, schema: Type.String({ minLength: 1 }) }];\nexport const apiContracts = defineApiContract({\n version: 1,\n dynamic: [],\n http: [\n {\n id: 'files.detail',\n scope: 'session',\n basePath: 'file-edits',\n path: '/detail',\n method: 'GET',\n authentication: 'owner',\n description: 'Read recorded changes and current file content.',\n parameters: query,\n responses: jsonApiResponses(FileDetailSchema),\n },\n {\n id: 'files.preview',\n scope: 'session',\n basePath: 'file-edits',\n path: '/preview',\n method: 'GET',\n authentication: 'owner',\n description: 'Read a file contained in the session directory.',\n parameters: query,\n responses: jsonApiResponses(Type.Object({ path: S, relPath: S, working: Working })),\n },\n {\n id: 'files.save',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'PUT',\n authentication: 'owner',\n description: 'Save a recorded file if its hash still matches.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({ path: S, expectedHash: S, content: S }),\n },\n responses: jsonApiResponses(Type.Object({ hash: S })),\n },\n {\n id: 'files.delete',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'DELETE',\n authentication: 'owner',\n description: 'Delete a recorded file.',\n parameters: query,\n responses: {\n '204': { description: 'File deleted or already absent.' },\n '404': { description: 'Unrecorded file.', schema: Type.Object({ error: S }) },\n '409': { description: 'Deletion failed.', schema: Type.Object({ error: S }) },\n },\n },\n ],\n sockets: (['global', 'workspace'] as const).map((scope) => ({\n id: 'files.channel',\n scope,\n service: 'doompi.hub.v1',\n member: 'file_edits',\n kind: 'channel',\n direction: 'server-to-client',\n description: 'File edits channel payload in the hub frame payload property.',\n input: FilesPayloadSchema,\n })),\n});\nexport default apiContracts;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
//#region src/schemas/apiContracts.d.ts
|
|
3
|
+
export declare const apiContracts: {
|
|
4
|
+
version: 1;
|
|
5
|
+
protocols?: Record<string, string> | undefined;
|
|
6
|
+
schemas?: Record<string, boolean | object> | undefined;
|
|
7
|
+
http: {
|
|
8
|
+
id: string;
|
|
9
|
+
scope: "global" | "session" | "workspace";
|
|
10
|
+
basePath?: string | undefined;
|
|
11
|
+
path: string;
|
|
12
|
+
method: "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT";
|
|
13
|
+
description: string;
|
|
14
|
+
availability?: string | undefined;
|
|
15
|
+
authentication: "device" | "none" | "owner";
|
|
16
|
+
parameters?: {
|
|
17
|
+
name: string;
|
|
18
|
+
in: "header" | "path" | "query";
|
|
19
|
+
required: boolean;
|
|
20
|
+
schema: boolean | object;
|
|
21
|
+
}[] | undefined;
|
|
22
|
+
body?: {
|
|
23
|
+
contentType: string;
|
|
24
|
+
contentTypes?: string[] | undefined;
|
|
25
|
+
required: boolean;
|
|
26
|
+
schema: boolean | object;
|
|
27
|
+
} | undefined;
|
|
28
|
+
responses: Record<string, {
|
|
29
|
+
description: string;
|
|
30
|
+
contentType?: string | undefined;
|
|
31
|
+
schema?: boolean | object | undefined;
|
|
32
|
+
events?: Record<string, boolean | object> | undefined;
|
|
33
|
+
}>;
|
|
34
|
+
}[];
|
|
35
|
+
sockets: {
|
|
36
|
+
id: string;
|
|
37
|
+
scope: "global" | "session" | "workspace";
|
|
38
|
+
description: string;
|
|
39
|
+
availability?: string | undefined;
|
|
40
|
+
service: string;
|
|
41
|
+
member: string;
|
|
42
|
+
direction: "client-to-server" | "server-to-client";
|
|
43
|
+
kind: "channel" | "method" | "state";
|
|
44
|
+
input: boolean | object;
|
|
45
|
+
output?: boolean | object | undefined;
|
|
46
|
+
errors?: boolean | object | undefined;
|
|
47
|
+
}[];
|
|
48
|
+
dynamic: string[];
|
|
49
|
+
gaps?: string[] | undefined;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
export { apiContracts as default };
|
|
53
|
+
//# sourceMappingURL=apiContracts.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiContracts.d.mts","names":[],"sources":["../src/schemas/apiContracts.ts"],"mappings":";;qBA0Ba"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { defineApiContract, jsonApiResponses } from "@agimon-ai/doompi-core/apiContracts";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
//#region src/schemas/apiContracts.ts
|
|
4
|
+
const S = Type.String();
|
|
5
|
+
const N = Type.Number();
|
|
6
|
+
const B = Type.Boolean();
|
|
7
|
+
const O = Type.Optional;
|
|
8
|
+
const literals = (values) => Type.Union(values.map((value) => Type.Literal(value)));
|
|
9
|
+
const Tool = literals([
|
|
10
|
+
"edit",
|
|
11
|
+
"write",
|
|
12
|
+
"bash",
|
|
13
|
+
"user"
|
|
14
|
+
]);
|
|
15
|
+
const Hunk = Type.Object({
|
|
16
|
+
start: N,
|
|
17
|
+
rows: Type.Array(Type.Object({
|
|
18
|
+
marker: literals([
|
|
19
|
+
"+",
|
|
20
|
+
"-",
|
|
21
|
+
" "
|
|
22
|
+
]),
|
|
23
|
+
line: N,
|
|
24
|
+
content: S
|
|
25
|
+
}))
|
|
26
|
+
});
|
|
27
|
+
const Diff = {
|
|
28
|
+
additions: N,
|
|
29
|
+
removals: N,
|
|
30
|
+
hunks: O(Type.Array(Hunk)),
|
|
31
|
+
note: O(S)
|
|
32
|
+
};
|
|
33
|
+
const Working = Type.Object({
|
|
34
|
+
content: S,
|
|
35
|
+
hash: S,
|
|
36
|
+
unavailable: B,
|
|
37
|
+
reason: O(S)
|
|
38
|
+
});
|
|
39
|
+
const FileDetailSchema = Type.Object({
|
|
40
|
+
path: S,
|
|
41
|
+
relPath: S,
|
|
42
|
+
versions: Type.Array(Type.Object({
|
|
43
|
+
index: N,
|
|
44
|
+
tool: Tool,
|
|
45
|
+
at: N,
|
|
46
|
+
origin: literals(["tool", "scan"]),
|
|
47
|
+
...Diff
|
|
48
|
+
})),
|
|
49
|
+
cumulative: Type.Object(Diff),
|
|
50
|
+
working: Working
|
|
51
|
+
});
|
|
52
|
+
const FilesPayloadSchema = Type.Object({ items: Type.Array(Type.Object({
|
|
53
|
+
path: S,
|
|
54
|
+
relPath: S,
|
|
55
|
+
tool: Tool,
|
|
56
|
+
at: N,
|
|
57
|
+
count: N,
|
|
58
|
+
diffable: B
|
|
59
|
+
})) });
|
|
60
|
+
const query = [{
|
|
61
|
+
name: "path",
|
|
62
|
+
in: "query",
|
|
63
|
+
required: true,
|
|
64
|
+
schema: Type.String({ minLength: 1 })
|
|
65
|
+
}];
|
|
66
|
+
const apiContracts = defineApiContract({
|
|
67
|
+
version: 1,
|
|
68
|
+
dynamic: [],
|
|
69
|
+
http: [
|
|
70
|
+
{
|
|
71
|
+
id: "files.detail",
|
|
72
|
+
scope: "session",
|
|
73
|
+
basePath: "file-edits",
|
|
74
|
+
path: "/detail",
|
|
75
|
+
method: "GET",
|
|
76
|
+
authentication: "owner",
|
|
77
|
+
description: "Read recorded changes and current file content.",
|
|
78
|
+
parameters: query,
|
|
79
|
+
responses: jsonApiResponses(FileDetailSchema)
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "files.preview",
|
|
83
|
+
scope: "session",
|
|
84
|
+
basePath: "file-edits",
|
|
85
|
+
path: "/preview",
|
|
86
|
+
method: "GET",
|
|
87
|
+
authentication: "owner",
|
|
88
|
+
description: "Read a file contained in the session directory.",
|
|
89
|
+
parameters: query,
|
|
90
|
+
responses: jsonApiResponses(Type.Object({
|
|
91
|
+
path: S,
|
|
92
|
+
relPath: S,
|
|
93
|
+
working: Working
|
|
94
|
+
}))
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "files.save",
|
|
98
|
+
scope: "session",
|
|
99
|
+
basePath: "file-edits",
|
|
100
|
+
path: "/content",
|
|
101
|
+
method: "PUT",
|
|
102
|
+
authentication: "owner",
|
|
103
|
+
description: "Save a recorded file if its hash still matches.",
|
|
104
|
+
body: {
|
|
105
|
+
required: true,
|
|
106
|
+
contentType: "application/json",
|
|
107
|
+
schema: Type.Object({
|
|
108
|
+
path: S,
|
|
109
|
+
expectedHash: S,
|
|
110
|
+
content: S
|
|
111
|
+
})
|
|
112
|
+
},
|
|
113
|
+
responses: jsonApiResponses(Type.Object({ hash: S }))
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "files.delete",
|
|
117
|
+
scope: "session",
|
|
118
|
+
basePath: "file-edits",
|
|
119
|
+
path: "/content",
|
|
120
|
+
method: "DELETE",
|
|
121
|
+
authentication: "owner",
|
|
122
|
+
description: "Delete a recorded file.",
|
|
123
|
+
parameters: query,
|
|
124
|
+
responses: {
|
|
125
|
+
"204": { description: "File deleted or already absent." },
|
|
126
|
+
"404": {
|
|
127
|
+
description: "Unrecorded file.",
|
|
128
|
+
schema: Type.Object({ error: S })
|
|
129
|
+
},
|
|
130
|
+
"409": {
|
|
131
|
+
description: "Deletion failed.",
|
|
132
|
+
schema: Type.Object({ error: S })
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
sockets: ["global", "workspace"].map((scope) => ({
|
|
138
|
+
id: "files.channel",
|
|
139
|
+
scope,
|
|
140
|
+
service: "doompi.hub.v1",
|
|
141
|
+
member: "file_edits",
|
|
142
|
+
kind: "channel",
|
|
143
|
+
direction: "server-to-client",
|
|
144
|
+
description: "File edits channel payload in the hub frame payload property.",
|
|
145
|
+
input: FilesPayloadSchema
|
|
146
|
+
}))
|
|
147
|
+
});
|
|
148
|
+
//#endregion
|
|
149
|
+
export { apiContracts, apiContracts as default };
|
|
150
|
+
|
|
151
|
+
//# sourceMappingURL=apiContracts.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiContracts.mjs","names":[],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract, jsonApiResponses } from '@agimon-ai/doompi-core/apiContracts';\nimport { Type } from 'typebox';\n\nconst S = Type.String();\nconst N = Type.Number();\nconst B = Type.Boolean();\nconst O = Type.Optional;\nconst literals = <T extends string>(values: T[]) => Type.Union(values.map((value) => Type.Literal(value)));\nconst Tool = literals(['edit', 'write', 'bash', 'user']);\nconst Hunk = Type.Object({\n start: N,\n rows: Type.Array(Type.Object({ marker: literals(['+', '-', ' ']), line: N, content: S })),\n});\nconst Diff = { additions: N, removals: N, hunks: O(Type.Array(Hunk)), note: O(S) };\nconst Working = Type.Object({ content: S, hash: S, unavailable: B, reason: O(S) });\nexport const FileDetailSchema = Type.Object({\n path: S,\n relPath: S,\n versions: Type.Array(Type.Object({ index: N, tool: Tool, at: N, origin: literals(['tool', 'scan']), ...Diff })),\n cumulative: Type.Object(Diff),\n working: Working,\n});\nexport const FilesPayloadSchema = Type.Object({\n items: Type.Array(Type.Object({ path: S, relPath: S, tool: Tool, at: N, count: N, diffable: B })),\n});\nconst query = [{ name: 'path', in: 'query' as const, required: true, schema: Type.String({ minLength: 1 }) }];\nexport const apiContracts = defineApiContract({\n version: 1,\n dynamic: [],\n http: [\n {\n id: 'files.detail',\n scope: 'session',\n basePath: 'file-edits',\n path: '/detail',\n method: 'GET',\n authentication: 'owner',\n description: 'Read recorded changes and current file content.',\n parameters: query,\n responses: jsonApiResponses(FileDetailSchema),\n },\n {\n id: 'files.preview',\n scope: 'session',\n basePath: 'file-edits',\n path: '/preview',\n method: 'GET',\n authentication: 'owner',\n description: 'Read a file contained in the session directory.',\n parameters: query,\n responses: jsonApiResponses(Type.Object({ path: S, relPath: S, working: Working })),\n },\n {\n id: 'files.save',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'PUT',\n authentication: 'owner',\n description: 'Save a recorded file if its hash still matches.',\n body: {\n required: true,\n contentType: 'application/json',\n schema: Type.Object({ path: S, expectedHash: S, content: S }),\n },\n responses: jsonApiResponses(Type.Object({ hash: S })),\n },\n {\n id: 'files.delete',\n scope: 'session',\n basePath: 'file-edits',\n path: '/content',\n method: 'DELETE',\n authentication: 'owner',\n description: 'Delete a recorded file.',\n parameters: query,\n responses: {\n '204': { description: 'File deleted or already absent.' },\n '404': { description: 'Unrecorded file.', schema: Type.Object({ error: S }) },\n '409': { description: 'Deletion failed.', schema: Type.Object({ error: S }) },\n },\n },\n ],\n sockets: (['global', 'workspace'] as const).map((scope) => ({\n id: 'files.channel',\n scope,\n service: 'doompi.hub.v1',\n member: 'file_edits',\n kind: 'channel',\n direction: 'server-to-client',\n description: 'File edits channel payload in the hub frame payload property.',\n input: FilesPayloadSchema,\n })),\n});\nexport default apiContracts;\n"],"mappings":";;;AAGA,MAAM,IAAI,KAAK,OAAO;AACtB,MAAM,IAAI,KAAK,OAAO;AACtB,MAAM,IAAI,KAAK,QAAQ;AACvB,MAAM,IAAI,KAAK;AACf,MAAM,YAA8B,WAAgB,KAAK,MAAM,OAAO,KAAK,UAAU,KAAK,QAAQ,KAAK,CAAC,CAAC;AACzG,MAAM,OAAO,SAAS;CAAC;CAAQ;CAAS;CAAQ;AAAM,CAAC;AACvD,MAAM,OAAO,KAAK,OAAO;CACvB,OAAO;CACP,MAAM,KAAK,MAAM,KAAK,OAAO;EAAE,QAAQ,SAAS;GAAC;GAAK;GAAK;EAAG,CAAC;EAAG,MAAM;EAAG,SAAS;CAAE,CAAC,CAAC;AAC1F,CAAC;AACD,MAAM,OAAO;CAAE,WAAW;CAAG,UAAU;CAAG,OAAO,EAAE,KAAK,MAAM,IAAI,CAAC;CAAG,MAAM,EAAE,CAAC;AAAE;AACjF,MAAM,UAAU,KAAK,OAAO;CAAE,SAAS;CAAG,MAAM;CAAG,aAAa;CAAG,QAAQ,EAAE,CAAC;AAAE,CAAC;AACjF,MAAa,mBAAmB,KAAK,OAAO;CAC1C,MAAM;CACN,SAAS;CACT,UAAU,KAAK,MAAM,KAAK,OAAO;EAAE,OAAO;EAAG,MAAM;EAAM,IAAI;EAAG,QAAQ,SAAS,CAAC,QAAQ,MAAM,CAAC;EAAG,GAAG;CAAK,CAAC,CAAC;CAC9G,YAAY,KAAK,OAAO,IAAI;CAC5B,SAAS;AACX,CAAC;AACD,MAAa,qBAAqB,KAAK,OAAO,EAC5C,OAAO,KAAK,MAAM,KAAK,OAAO;CAAE,MAAM;CAAG,SAAS;CAAG,MAAM;CAAM,IAAI;CAAG,OAAO;CAAG,UAAU;AAAE,CAAC,CAAC,EAClG,CAAC;AACD,MAAM,QAAQ,CAAC;CAAE,MAAM;CAAQ,IAAI;CAAkB,UAAU;CAAM,QAAQ,KAAK,OAAO,EAAE,WAAW,EAAE,CAAC;AAAE,CAAC;AAC5G,MAAa,eAAe,kBAAkB;CAC5C,SAAS;CACT,SAAS,CAAC;CACV,MAAM;EACJ;GACE,IAAI;GACJ,OAAO;GACP,UAAU;GACV,MAAM;GACN,QAAQ;GACR,gBAAgB;GAChB,aAAa;GACb,YAAY;GACZ,WAAW,iBAAiB,gBAAgB;EAC9C;EACA;GACE,IAAI;GACJ,OAAO;GACP,UAAU;GACV,MAAM;GACN,QAAQ;GACR,gBAAgB;GAChB,aAAa;GACb,YAAY;GACZ,WAAW,iBAAiB,KAAK,OAAO;IAAE,MAAM;IAAG,SAAS;IAAG,SAAS;GAAQ,CAAC,CAAC;EACpF;EACA;GACE,IAAI;GACJ,OAAO;GACP,UAAU;GACV,MAAM;GACN,QAAQ;GACR,gBAAgB;GAChB,aAAa;GACb,MAAM;IACJ,UAAU;IACV,aAAa;IACb,QAAQ,KAAK,OAAO;KAAE,MAAM;KAAG,cAAc;KAAG,SAAS;IAAE,CAAC;GAC9D;GACA,WAAW,iBAAiB,KAAK,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;EACtD;EACA;GACE,IAAI;GACJ,OAAO;GACP,UAAU;GACV,MAAM;GACN,QAAQ;GACR,gBAAgB;GAChB,aAAa;GACb,YAAY;GACZ,WAAW;IACT,OAAO,EAAE,aAAa,kCAAkC;IACxD,OAAO;KAAE,aAAa;KAAoB,QAAQ,KAAK,OAAO,EAAE,OAAO,EAAE,CAAC;IAAE;IAC5E,OAAO;KAAE,aAAa;KAAoB,QAAQ,KAAK,OAAO,EAAE,OAAO,EAAE,CAAC;IAAE;GAC9E;EACF;CACF;CACA,SAAU,CAAC,UAAU,WAAW,CAAC,CAAW,KAAK,WAAW;EAC1D,IAAI;EACJ;EACA,SAAS;EACT,QAAQ;EACR,MAAM;EACN,WAAW;EACX,aAAa;EACb,OAAO;CACT,EAAE;AACJ,CAAC"}
|