pindeck 3.2.0-beta.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -26
- package/front.mjs +1 -0
- package/lib/capabilities.mjs +2 -2
- package/lib/platform-notice.mjs +2 -2
- package/package.json +2 -2
- package/public/editor.js +1 -1
- package/server.mjs +25 -16
package/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# PinDeck
|
|
2
2
|
|
|
3
|
-
> **macOS
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
> releases (`npx pindeck`) stay on 2.x. See installation below.
|
|
3
|
+
> **macOS and Linux · Version 3** (`3.3.0`). On Linux, PinDeck runs as a headless server that you use
|
|
4
|
+
> from your Mac's browser through an SSH tunnel (see [Running on a Linux server](#running-on-a-linux-server)).
|
|
5
|
+
> Windows is not supported. Coming from PinDeck 2: version 3 keeps its settings and chats in
|
|
6
|
+
> `~/.pindeck-v3`, apart from 2.x (see [Models, providers and configuration](#models-providers-and-configuration)).
|
|
8
7
|
|
|
9
8
|
**Pin it. Say it. Done.** PinDeck is a local editor for HTML slide decks (reveal.js): the slides on
|
|
10
9
|
the left, the slide in the middle, and a chat with Claude Code or OpenAI Codex on the right. You click elements to
|
|
@@ -32,29 +31,28 @@ checked the layout; the next request, still being typed, refers to the new pins
|
|
|
32
31
|
|
|
33
32
|
## Quick start
|
|
34
33
|
|
|
35
|
-
Runs on macOS
|
|
34
|
+
Runs on macOS and Linux. The npm package declares `"os": ["darwin", "linux"]`, so normal
|
|
36
35
|
npm installation rejects other operating systems. Overriding that check does not make those platforms
|
|
37
36
|
supported. Running the server from a cloned repository on another OS shows a local support notice
|
|
38
37
|
instead of opening the deck or starting a backend.
|
|
39
38
|
|
|
40
|
-
Install
|
|
39
|
+
Install from npm:
|
|
41
40
|
|
|
42
41
|
```bash
|
|
43
|
-
npx pindeck
|
|
44
|
-
# Or select this exact beta:
|
|
45
|
-
npx pindeck@3.2.0-beta.1 /path/to/deck
|
|
42
|
+
npx pindeck /path/to/deck
|
|
46
43
|
```
|
|
47
44
|
|
|
48
|
-
Or run it from
|
|
45
|
+
Or run it from source, which includes the example deck:
|
|
49
46
|
|
|
50
47
|
```bash
|
|
51
|
-
git clone
|
|
52
|
-
cd PinDeck
|
|
48
|
+
git clone https://github.com/ehabets/PinDeck.git
|
|
49
|
+
cd PinDeck
|
|
53
50
|
node front.mjs ocean-example
|
|
54
51
|
```
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
Releases, with notes for each
|
|
53
|
+
PinDeck 2 stays available as `npx pindeck@2 /path/to/deck` (source: the
|
|
54
|
+
[`2.x`](https://github.com/ehabets/PinDeck/tree/2.x) branch). Releases, with notes for each version, are on
|
|
55
|
+
[GitHub](https://github.com/ehabets/PinDeck/releases).
|
|
58
56
|
|
|
59
57
|
Then open the login link PinDeck prints (`PinDeck: http://127.0.0.1:5180/login?code=...`). It works
|
|
60
58
|
once and logs this browser in; for a new one, run `pindeck login` (or `node front.mjs login`). On a Mac,
|
|
@@ -94,11 +92,11 @@ the deck, whichever port it runs on.
|
|
|
94
92
|
The corresponding executable must be on `PATH`; `CLAUDE_BIN` and `CODEX_BIN` can override its path.
|
|
95
93
|
Only the selected backend is required for editing. Model and effort availability comes from that
|
|
96
94
|
backend and account, and may differ from the provider’s website or desktop app.
|
|
97
|
-
* **Platforms:** macOS
|
|
95
|
+
* **Platforms:** macOS and Linux (see [Running on a Linux server](#running-on-a-linux-server)).
|
|
98
96
|
Windows is not supported. The npm package declares `"os": ["darwin", "linux"]`, so normal npm
|
|
99
97
|
installation rejects other operating systems. This check can be overridden; bypassing it does not make
|
|
100
98
|
those platforms supported. Source launches on unsupported platforms show a local support page without
|
|
101
|
-
opening the deck or starting a backend. Native Windows support is a possible v4 feature, not part of
|
|
99
|
+
opening the deck or starting a backend. Native Windows support is a possible v4 feature, not part of version 3.
|
|
102
100
|
* **Screenshots and PDF export:** require Chrome, Chromium, Edge or Brave where the server runs.
|
|
103
101
|
* **PDF input through Codex:** pages are rendered with macOS PDFKit, or Poppler on Linux. Page-text
|
|
104
102
|
extraction uses `osascript` and is macOS-only; on Linux Codex reads the rendered page image. Claude uses
|
|
@@ -266,7 +264,7 @@ from resuming the other's IDs. `--provider` and `--model` override the deck's se
|
|
|
266
264
|
is back on the deck's own choice. A later menu selection is saved. `CLAUDE_BIN` and `CODEX_BIN` override
|
|
267
265
|
executable paths. `CLAUDE_MODEL`, which PinDeck 2 read, is not used: PinDeck warns and names `--model`.
|
|
268
266
|
|
|
269
|
-
|
|
267
|
+
PinDeck 3 defaults to **`~/.pindeck-v3`**, keeping v2 settings and conversations separate. It does
|
|
270
268
|
not copy the v2 directory automatically; `config.json` and `decks/*/chat.json` can be copied over by hand
|
|
271
269
|
(asset roots too: they are in config.json). Never give both versions the same `PINDECK_HOME`: they keep files
|
|
272
270
|
of the same names in different formats and overwrite each other's settings and chats. PinDeck 3 warns at
|
|
@@ -308,7 +306,7 @@ library folder, add the folder it points to as an asset root; it does not have t
|
|
|
308
306
|
PinDeck never writes or deletes through a symlink out of the deck folder: with a `figures/` or `export/`
|
|
309
307
|
that links elsewhere, uploads, exports and deleting a picture's file are refused with the reason.
|
|
310
308
|
|
|
311
|
-
## Running on a Linux server
|
|
309
|
+
## Running on a Linux server
|
|
312
310
|
|
|
313
311
|
PinDeck can run on a Linux machine where the deck lives, with the editor in your Mac's browser. The
|
|
314
312
|
deck, PinDeck, the backend (Claude Code or Codex) and the deck's git clone all stay on the server; the browser only shows the
|
|
@@ -339,7 +337,7 @@ Other users of a shared server cannot open the editor or read the deck through P
|
|
|
339
337
|
protects it is only in your browser's cookie and in PinDeck's own files in `~/.pindeck-v3` (readable only by
|
|
340
338
|
you), never in a page or on a command line.
|
|
341
339
|
|
|
342
|
-
What works on Linux
|
|
340
|
+
What works on Linux: editing by hand, speaker notes, slides, pins, the chat with Claude or Codex,
|
|
343
341
|
Undo/Redo, live reload when files change on the server, layout checks, attaching figures, audio and
|
|
344
342
|
PDFs, `git-setup` and the PowerPoint import. Some features use programs on the server, and are on when
|
|
345
343
|
they are installed (on Debian or Ubuntu: `sudo apt install poppler-utils libreoffice-impress ffmpeg`):
|
|
@@ -351,7 +349,7 @@ they are installed (on Debian or Ubuntu: `sudo apt install poppler-utils libreof
|
|
|
351
349
|
| Pictures of PowerPoint slides in the import (thumbnails, diagrams) | LibreOffice (`soffice`) and Poppler |
|
|
352
350
|
| Waveforms of audio other than WAV in the import | ffmpeg |
|
|
353
351
|
|
|
354
|
-
Without them the editor says what is missing (hover over "Server: Linux
|
|
352
|
+
Without them the editor says what is missing (hover over "Server: Linux"), and the assistant is told
|
|
355
353
|
which commands are not available. LibreOffice may draw some slides a little differently from PowerPoint;
|
|
356
354
|
the pictures are references and figures, the slides themselves are rebuilt from the file. Chromium from snap may not be
|
|
357
355
|
allowed to use hidden folders in your home folder such as `~/.pindeck-v3`; if screenshots fail with it, set
|
|
@@ -494,8 +492,8 @@ Without PowerPoint or Keynote (or on other systems than macOS) only the text and
|
|
|
494
492
|
from `lib/system-prompt.md` and the deck's `CLAUDE.md` and `AGENTS.md`. Codex model and effort changes
|
|
495
493
|
reuse the active thread writer and apply to the next turn. Claude changes and provider switches prepare
|
|
496
494
|
a replacement process before retiring the old one. Events from replaced sessions are ignored.
|
|
497
|
-
* **Permissions.** The assistant may read
|
|
498
|
-
editor's CLI. Nothing else: no other commands, and no edits to `brand.css`, `vendor/`, `.git/` or, for a
|
|
495
|
+
* **Permissions.** The assistant may read the deck folder, its screenshots, attached PDFs and asset roots,
|
|
496
|
+
edit and create files in the deck folder, and run the editor's CLI. Nothing else: no other commands, and no edits to `brand.css`, `vendor/`, `.git/` or, for a
|
|
499
497
|
named deck file, the other decks in its folder. Claude Code loads no settings files and no MCP servers,
|
|
500
498
|
so no allow rule in your own, the deck's or a parent folder's settings widens this. Codex has no shell
|
|
501
499
|
and no file access of its own: it reads and edits through PinDeck's own tools, which apply the same
|
|
@@ -555,12 +553,30 @@ Without PowerPoint or Keynote (or on other systems than macOS) only the text and
|
|
|
555
553
|
* One writer at a time: PinDeck is not made for editing the same deck from two editors or tools at once.
|
|
556
554
|
* A deck's own scripts run sandboxed in the editor and in Present: they cannot use `localStorage`, cookies
|
|
557
555
|
or the editor page. A deck that needs them for its slides to work should catch the error.
|
|
558
|
-
*
|
|
559
|
-
|
|
560
|
-
Poppler, LibreOffice and ffmpeg on the server (see [Running on a Linux server](#running-on-a-linux-server-beta)).
|
|
556
|
+
* Linux: pictures of PDF pages and PowerPoint slides, and waveforms of non-WAV audio, need
|
|
557
|
+
Poppler, LibreOffice and ffmpeg on the server (see [Running on a Linux server](#running-on-a-linux-server)).
|
|
561
558
|
|
|
562
559
|
## Versions
|
|
563
560
|
|
|
561
|
+
* **3.3.0**: fixes from a code audit.
|
|
562
|
+
- After a PowerPoint import, refining the slides no longer deletes pictures that the deck still uses
|
|
563
|
+
(when the turn was stopped, failed or only changed `theme.css`); Undo brings removed pictures back.
|
|
564
|
+
- Long texts with non-ASCII characters (large notes, tables, SVG) no longer get broken characters.
|
|
565
|
+
- Claude reads only the deck, its screenshots, attached PDFs and asset roots, as Codex already did, and
|
|
566
|
+
cannot create a `.git` file in the deck folder.
|
|
567
|
+
|
|
568
|
+
* **3.2.0**: PinDeck 3, the first stable release of version 3: everything from the 3.0, 3.1 and 3.2 betas
|
|
569
|
+
below. Compared with 2.11:
|
|
570
|
+
- OpenAI Codex as a second assistant next to Claude Code, with the models and efforts each backend
|
|
571
|
+
offers; each deck keeps its own provider, model and effort.
|
|
572
|
+
- Several decks on one port: a deck menu above the slide, each deck with its own chat, assistant
|
|
573
|
+
session and Undo.
|
|
574
|
+
- Deck pages run sandboxed, so a deck's script cannot use the API or read other decks.
|
|
575
|
+
- Linux is no longer a beta.
|
|
576
|
+
- Settings and chats are kept in `~/.pindeck-v3`, apart from PinDeck 2's `~/.pindeck`; nothing is copied
|
|
577
|
+
over (see [Models, providers and configuration](#models-providers-and-configuration)). PinDeck 2 stays
|
|
578
|
+
on npm as `pindeck@2`.
|
|
579
|
+
|
|
564
580
|
* **3.2.0-beta.1**: fixes from a code review and a round of user tests.
|
|
565
581
|
- Security: deck pages run sandboxed, so a deck's script can no longer use the API, read other decks or
|
|
566
582
|
send messages; the login link's `next` stays on PinDeck. A deck's own script cannot use `localStorage`
|
|
@@ -621,6 +637,11 @@ Without PowerPoint or Keynote (or on other systems than macOS) only the text and
|
|
|
621
637
|
switching with optional handover, curated model lists, a separate effort selector with per-model
|
|
622
638
|
preferences, restricted Codex tools, and separate v3 runtime state. On npm as `pindeck@beta`.
|
|
623
639
|
|
|
640
|
+
* **2.11**: screenshots in the chat. A screenshot sent with a message, and one Claude reads, shows as
|
|
641
|
+
a small picture that opens larger with a click (Esc or a click closes it). PinDeck keeps the 40
|
|
642
|
+
newest screenshots of a deck; an older one says "no longer kept". Chats from before 2.11 keep their
|
|
643
|
+
plain label. (In 3.x since 3.1.0-beta.1.)
|
|
644
|
+
|
|
624
645
|
* **2.10**: decks with their own file names and shared files, decks made without reveal.js, and Claude
|
|
625
646
|
working on the whole deck.
|
|
626
647
|
- A deck file by name: `pindeck path/to/talk.html` (a folder still opens its `index.html`). Several
|
package/front.mjs
CHANGED
|
@@ -243,6 +243,7 @@ function follow(st, tab, key) {
|
|
|
243
243
|
}
|
|
244
244
|
async function readJson(req) {
|
|
245
245
|
let s = "";
|
|
246
|
+
req.setEncoding("utf8");
|
|
246
247
|
for await (const c of req) { s += c; if (s.length > 10000) throw new Error("body too large"); }
|
|
247
248
|
return s ? JSON.parse(s) : {};
|
|
248
249
|
}
|
package/lib/capabilities.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* What this server can do on this machine, decided once on the backend and used everywhere: the editor
|
|
2
2
|
(status), the API (refusals before any side effect), the CLI (its error text) and the assistant's system prompt.
|
|
3
3
|
|
|
4
|
-
macOS: everything, as before. Linux
|
|
4
|
+
macOS: everything, as before. Linux: editing, the chat, live reload and the PowerPoint import;
|
|
5
5
|
pictures of PDF pages with Poppler, pictures of PowerPoint slides with LibreOffice and Poppler, and
|
|
6
6
|
waveforms of non-WAV audio with ffmpeg, each when installed, else off with a reason. Chrome features (screenshots, headless
|
|
7
7
|
layout, PDF export) work on Linux as on macOS when Chrome or Chromium is found; PINDECK_LINUX_CHROME=0
|
|
@@ -12,7 +12,7 @@ import path from "node:path";
|
|
|
12
12
|
|
|
13
13
|
export const SUPPORTED_PLATFORMS = ["darwin", "linux"];
|
|
14
14
|
export const isSupportedPlatform = (platform) => SUPPORTED_PLATFORMS.includes(platform);
|
|
15
|
-
export const platformLabel = (platform) => ({ darwin: "macOS", linux: "Linux
|
|
15
|
+
export const platformLabel = (platform) => ({ darwin: "macOS", linux: "Linux" }[platform] || platform);
|
|
16
16
|
|
|
17
17
|
const CHROME_PATHS = {
|
|
18
18
|
darwin: ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
package/lib/platform-notice.mjs
CHANGED
|
@@ -4,8 +4,8 @@ export function createPlatformNoticeServer(platform) {
|
|
|
4
4
|
const windows = platform === 'win32';
|
|
5
5
|
const heading = windows ? 'Windows support is on the roadmap' : 'PinDeck runs on macOS and Linux';
|
|
6
6
|
const explanation = windows
|
|
7
|
-
? 'PinDeck runs on macOS and
|
|
8
|
-
: 'This version of PinDeck runs on macOS and
|
|
7
|
+
? 'PinDeck runs on macOS and Linux. Windows support is planned for a future version; there is no release date yet.'
|
|
8
|
+
: 'This version of PinDeck runs on macOS and Linux. This operating system is not supported.';
|
|
9
9
|
const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>PinDeck · system not supported</title><style>
|
|
10
10
|
:root{font-family:system-ui,sans-serif;color:#243348;background:#f4f6f8;color-scheme:light}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px}main{width:min(100%,620px);padding:40px;background:white;border:1px solid #dde3ea;border-radius:18px;box-shadow:0 12px 40px #20334d0a}.brand{font-weight:700;color:#c36516;margin:0 0 32px}h1{font-size:clamp(26px,5vw,36px);line-height:1.2;margin:0 0 20px}p{line-height:1.65}a{color:#945012}footer{margin-top:28px;font-size:14px;color:#596577}
|
|
11
11
|
</style></head><body><main><p class="brand">PinDeck</p><h1>${heading}</h1><p>${explanation}</p><p>Your deck has not been opened or changed, and no AI backend has been started.</p><p>You can run PinDeck on a Mac or a Linux machine (and use it from this browser through an SSH tunnel), or follow updates on <a href="https://github.com/ehabets/PinDeck">GitHub</a>.</p><footer>Close this tab and press Ctrl+C in the terminal to stop this notice.</footer></main></body></html>`;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pindeck",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "PinDeck: an editor for HTML slide decks on macOS, and on Linux
|
|
3
|
+
"version": "3.3.0",
|
|
4
|
+
"description": "PinDeck: an editor for HTML slide decks on macOS, and on Linux through an SSH tunnel. Pin elements, describe the change, and Claude Code or OpenAI Codex edits the slides.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"slides",
|
|
7
7
|
"presentation",
|
package/public/editor.js
CHANGED
|
@@ -907,7 +907,7 @@ function applyStatus(s) {
|
|
|
907
907
|
imp.disabled = !!why(caps.pptxImport);
|
|
908
908
|
imp.title = why(caps.pptxImport);
|
|
909
909
|
if (why(caps[provider])) $("conn").title = caps[provider].reason;
|
|
910
|
-
// where the server runs, when it is not this Mac's usual setup: "Linux
|
|
910
|
+
// where the server runs, when it is not this Mac's usual setup: "Linux", with what is off
|
|
911
911
|
const names = { claude: "Claude", codex: "OpenAI Codex", screenshot: "Screenshots", headlessLayout: "Layout without an open editor",
|
|
912
912
|
pdfExport: "PDF export", pdfPages: "Pictures of PDF pages", pptxImport: "PowerPoint import",
|
|
913
913
|
pptxPictures: "Pictures of PowerPoint slides", audioConvert: "Waveforms of non-WAV audio" };
|
package/server.mjs
CHANGED
|
@@ -243,6 +243,7 @@ function staleEdit(res, b) {
|
|
|
243
243
|
const text = (res, code, s) => { res.writeHead(code, { "content-type": "text/plain; charset=utf-8" }); res.end(s); };
|
|
244
244
|
async function body(req) {
|
|
245
245
|
let s = "";
|
|
246
|
+
req.setEncoding("utf8");
|
|
246
247
|
for await (const c of req) { s += c; if (s.length > 5e6) throw new Error("body too large"); }
|
|
247
248
|
return s ? JSON.parse(s) : {};
|
|
248
249
|
}
|
|
@@ -576,7 +577,7 @@ function slidesInstructions() {
|
|
|
576
577
|
win over allow rules; a locked file changed anyway is restored after the turn (lock 2 in finishTurn). */
|
|
577
578
|
function lockedPaths() {
|
|
578
579
|
const { files, folders } = otherDecks();
|
|
579
|
-
return [...LOCKED, "vendor/**", ".git/**", ...files, ...folders.map((f) => `${f}/**`)];
|
|
580
|
+
return [...LOCKED, "vendor/**", ".git", ".git/**", ...files, ...folders.map((f) => `${f}/**`)];
|
|
580
581
|
}
|
|
581
582
|
/* The other decks in this deck's folder: its other .html files, and the subfolders that hold a deck of their
|
|
582
583
|
own (as the deck menu lists them). Neither the assistant nor this deck's undo touches them. */
|
|
@@ -612,10 +613,9 @@ function othersWorkedSince(start) {
|
|
|
612
613
|
for (const f of files) {
|
|
613
614
|
try {
|
|
614
615
|
const o = JSON.parse(fs.readFileSync(path.join(TURNS_DIR, f), "utf8"));
|
|
615
|
-
|
|
616
|
-
try { process.kill(o.pid, 0); } catch (e) { alive = e.code === "EPERM"; }
|
|
616
|
+
const running = alive(o.pid);
|
|
617
617
|
// a turn still open in a server that is gone ended when that server did, at the latest
|
|
618
|
-
if (o.turns.some((x) => (x.end ?? (
|
|
618
|
+
if (o.turns.some((x) => (x.end ?? (running ? now : x.start)) >= start)) names.push(o.deck);
|
|
619
619
|
} catch {}
|
|
620
620
|
}
|
|
621
621
|
return names;
|
|
@@ -671,7 +671,7 @@ function makeSession(choice) {
|
|
|
671
671
|
args: () => ["--append-system-prompt", systemPrompt + slidesInstructions() + deckInstructions(),
|
|
672
672
|
"--setting-sources", "", "--strict-mcp-config", "--safe-mode", "--disable-slash-commands", // no settings files, no MCP servers or connectors
|
|
673
673
|
"--permission-mode", "default", "--tools", "Read,Glob,Grep,Edit,Write,Bash",
|
|
674
|
-
"--allowedTools",
|
|
674
|
+
"--allowedTools", ...readRules(), "Edit(./**)", "Write(./**)",
|
|
675
675
|
...["layout", "shot", "slides", "pdf", "pdf-figure"].map(command => `Bash(${CLI_CMD} ${command}:*)`),
|
|
676
676
|
"--disallowedTools", ...lockedPaths().flatMap((f) => [`Edit(./${f})`, `Write(./${f})`]),
|
|
677
677
|
...(choice.model ? ["--model", choice.model] : []), ...(choice.effort ? ["--effort", choice.effort] : [])],
|
|
@@ -679,6 +679,13 @@ function makeSession(choice) {
|
|
|
679
679
|
agent.initialEvent = () => initial;
|
|
680
680
|
return agent;
|
|
681
681
|
}
|
|
682
|
+
/* Claude reads what Codex's tools may read (agentTools' readDirs): the deck, screenshots, attached PDFs and
|
|
683
|
+
asset roots, each by its path and its real path ("//" starts an absolute path in a rule) */
|
|
684
|
+
function readRules() {
|
|
685
|
+
const dirs = [SHOT_DIR, PDF_DIR, ...ASSET_ROOTS].flatMap((d) => { try { return [d, fs.realpathSync(d)]; } catch { return [d]; } });
|
|
686
|
+
const globs = ["./**", ...new Set(dirs.map((d) => `/${d}/**`))];
|
|
687
|
+
return ["Read", "Glob", "Grep"].flatMap((tool) => globs.map((g) => `${tool}(${g})`));
|
|
688
|
+
}
|
|
682
689
|
const providerAvailable = (provider) => detectCapabilities()[provider];
|
|
683
690
|
/* Every backend process that is stopped is waited for on exit: the discovery helpers, the session a switch
|
|
684
691
|
replaced, the current one. */
|
|
@@ -892,7 +899,7 @@ function handleEvent(ev) {
|
|
|
892
899
|
function startTurn(promptText, userEntry) {
|
|
893
900
|
// every deck file as it is now: the assistant may change any of them (not brand.css or vendor/), and undo
|
|
894
901
|
// takes back all it changed
|
|
895
|
-
state.turn = { id: userEntry.id, before: snapshot([...EDITABLE, ...LOCKED]), gitConfig: readDeck(".git/config"), scan: scanDeck(), touched: new Set(),
|
|
902
|
+
state.turn = { id: userEntry.id, before: snapshot([...EDITABLE, ...LOCKED]), gitConfig: readDeck(".git/config"), hadGit: fs.existsSync(path.join(DECK_DIR, ".git")), scan: scanDeck(), touched: new Set(),
|
|
896
903
|
time: { start: Date.now(), end: null } };
|
|
897
904
|
logTurnTime(state.turn.time);
|
|
898
905
|
state.busy = true;
|
|
@@ -930,6 +937,12 @@ function finishTurn({ error, cost, duration, denials = [] }) {
|
|
|
930
937
|
restored.push(".git/config");
|
|
931
938
|
} catch (e) { console.error(e.message); }
|
|
932
939
|
}
|
|
940
|
+
// a .git file ("gitdir: elsewhere") points git at another config, which the check above does not see
|
|
941
|
+
const dotGit = path.join(DECK_DIR, ".git");
|
|
942
|
+
if (!turn.hadGit && fs.lstatSync(dotGit, { throwIfNoEntry: false })?.isDirectory() === false) {
|
|
943
|
+
fs.rmSync(dotGit, { force: true });
|
|
944
|
+
restored.push(".git");
|
|
945
|
+
}
|
|
933
946
|
toolEntries.clear();
|
|
934
947
|
const eid = runEidPass();
|
|
935
948
|
const found = scanChanges(turn.scan, scanDeck());
|
|
@@ -952,13 +965,14 @@ function finishTurn({ error, cost, duration, denials = [] }) {
|
|
|
952
965
|
const deletedFiles = [];
|
|
953
966
|
for (const rel of turn.importedFiles || []) {
|
|
954
967
|
const abs = path.join(DECK_DIR, rel);
|
|
955
|
-
if (!fs.existsSync(abs) || EDITABLE.some((f) => (
|
|
968
|
+
if (!fs.existsSync(abs) || EDITABLE.some((f) => (readDeck(f) || "").includes(rel))) continue;
|
|
956
969
|
deletedFiles.push({ rel, data: fs.readFileSync(abs) });
|
|
957
970
|
fs.rmSync(abs, { force: true });
|
|
958
971
|
}
|
|
959
972
|
const t = { id: turn.id, kind: "agent", provider: settings.provider, conversation: state.conversation, before, after, files, deletedFiles, undone: false };
|
|
960
|
-
|
|
961
|
-
|
|
973
|
+
const kept = files.length || deletedFiles.length;
|
|
974
|
+
if (kept) { state.turns.push(t); state.redo = []; }
|
|
975
|
+
addEntry({ kind: "turn", turnId: kept ? t.id : null, files, error, cost, duration,
|
|
962
976
|
denials, restored, newIds: eid.assigned.length });
|
|
963
977
|
if (deletedFiles.length) addEntry({ kind: "notice", text: `Removed ${deletedFiles.map((f) => f.rel).join(", ")}: replaced, and no longer used.` });
|
|
964
978
|
if (notOurs.length) addEntry({ kind: "notice", text: `${notOurs.join(", ")} changed during this turn while the assistant of ` +
|
|
@@ -1243,11 +1257,7 @@ function undoConflicts(t, direction) {
|
|
|
1243
1257
|
const go = direction === "undo" ? t.addedFiles || [] : t.deletedFiles || [];
|
|
1244
1258
|
// a file to bring back or remove through a symlink out of the deck: not done (deckPath), so not begun
|
|
1245
1259
|
for (const { rel } of [...comeBack, ...go]) { try { deckPath(rel); } catch { files.push(rel); } }
|
|
1246
|
-
for (const { rel, data } of comeBack) {
|
|
1247
|
-
const abs = path.join(DECK_DIR, rel);
|
|
1248
|
-
if (fs.existsSync(abs) && !sameContent(abs, data)) files.push(rel);
|
|
1249
|
-
}
|
|
1250
|
-
for (const { rel, data } of go) {
|
|
1260
|
+
for (const { rel, data } of [...comeBack, ...go]) {
|
|
1251
1261
|
const abs = path.join(DECK_DIR, rel);
|
|
1252
1262
|
if (fs.existsSync(abs) && !sameContent(abs, data)) files.push(rel);
|
|
1253
1263
|
}
|
|
@@ -1330,8 +1340,7 @@ async function applyImport(imp, numbers, afterEid, refine = false) {
|
|
|
1330
1340
|
const afterIdx = found < 0 ? slides.length - 1 : found; // no such slide (any more): at the end
|
|
1331
1341
|
const after = slides[afterIdx];
|
|
1332
1342
|
const css = (readDeck("theme.css") || "") + (readDeck("brand.css") || "");
|
|
1333
|
-
const prefix = path.basename(imp.name, path.extname(imp.name)).
|
|
1334
|
-
.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").split("-").slice(0, 3).join("-") || "import";
|
|
1343
|
+
const prefix = slug(path.basename(imp.name, path.extname(imp.name))).split("-").slice(0, 3).join("-");
|
|
1335
1344
|
const r = await buildImport({ model: imp.model, numbers, pdf, workDir: imp.dir, deckDir: DECK_DIR, deckHtml: html0, css, after: afterIdx, prefix });
|
|
1336
1345
|
// building takes a while (pictures are cut from the slides): a change made meanwhile must not be overwritten
|
|
1337
1346
|
if (EDITABLE.some((f) => readDeck(f) !== before[f])) throw new Error("The deck changed during the import. Import again.");
|