@iamem/amem 0.1.1 → 0.1.3
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 +98 -4
- package/desktop/icons/icon-1024.png +0 -0
- package/desktop/icons/icon.icns +0 -0
- package/desktop/icons/icon.png +0 -0
- package/desktop/main.js +100 -0
- package/desktop/package.json +16 -0
- package/desktop/preload.js +4 -0
- package/desktop/scripts/ensure-electron.mjs +122 -0
- package/dist/api/routes.js +331 -1
- package/dist/app-shell.d.ts +13 -0
- package/dist/app-shell.js +126 -0
- package/dist/attest.d.ts +13 -0
- package/dist/attest.js +44 -0
- package/dist/cli.js +258 -2
- package/dist/context.d.ts +10 -1
- package/dist/context.js +105 -3
- package/dist/db.d.ts +141 -0
- package/dist/db.js +398 -0
- package/dist/embed.js +5 -14
- package/dist/hook.js +8 -1
- package/dist/hygiene.d.ts +1 -2
- package/dist/hygiene.js +1 -2
- package/dist/install/hosts.js +8 -0
- package/dist/install/skills.js +10 -5
- package/dist/license.d.ts +1 -0
- package/dist/license.js +21 -19
- package/dist/mcp.js +221 -0
- package/dist/policy.d.ts +6 -0
- package/dist/policy.js +16 -1
- package/dist/publish.d.ts +1 -1
- package/dist/publish.js +1 -0
- package/dist/skill-capture.d.ts +43 -0
- package/dist/skill-capture.js +146 -0
- package/dist/skills.d.ts +106 -0
- package/dist/skills.js +422 -0
- package/docs/backlog.md +9 -0
- package/docs/npm-release.md +1 -1
- package/package.json +7 -2
- package/scripts/postinstall.js +62 -0
- package/skills/amem-tasks/SKILL.md +100 -0
- package/skills/amem-write-skill/SKILL.md +99 -0
- package/templates/cursor-rule.mdc +16 -6
- package/templates/policy.deny-default.toml +5 -0
- package/templates/policy.example.toml +8 -0
- package/ui-static/app.js +435 -227
- package/ui-static/index.html +11 -34
- package/ui-static/styles.css +299 -0
package/README.md
CHANGED
|
@@ -52,7 +52,8 @@ Guarantees:
|
|
|
52
52
|
|
|
53
53
|
- `~/.amem` is created with mode `0700`
|
|
54
54
|
- Local UI binds to `127.0.0.1` only
|
|
55
|
-
-
|
|
55
|
+
- Memory never leaves the machine — no managed sync, no “share with org” mode
|
|
56
|
+
- Optional anonymous install ping only (see [Telemetry](#telemetry)); opt out anytime
|
|
56
57
|
- Agents are instructed to store **repo facts**, not proprietary prompting strategy
|
|
57
58
|
- Optional AES-256-GCM lock and encrypted local backups — still no cloud
|
|
58
59
|
|
|
@@ -87,6 +88,22 @@ See [docs/npm-release.md](docs/npm-release.md). CI runs `npm test` and `npm run
|
|
|
87
88
|
|
|
88
89
|
If `npm install` fails compiling native code, install Xcode CLT (macOS) or `build-essential` (Linux) and retry, or use a Node 20/22 official binary that matches the prebuild matrix.
|
|
89
90
|
|
|
91
|
+
### Telemetry
|
|
92
|
+
|
|
93
|
+
On `npm install` / `npx` (outside CI and tests), amem may send a single anonymous POST to `https://getamem.com/api/beacon/npm-install` with:
|
|
94
|
+
|
|
95
|
+
- package name and version
|
|
96
|
+
- Node.js version
|
|
97
|
+
- OS platform and CPU architecture
|
|
98
|
+
|
|
99
|
+
No code, paths, usernames, emails, IPs, or memory contents are included. Opt out:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
AMEM_TELEMETRY_DISABLED=1 npm i -g @iamem/amem
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Memory under `~/.amem` still never leaves your machine.
|
|
106
|
+
|
|
90
107
|
### Quick paths
|
|
91
108
|
|
|
92
109
|
```bash
|
|
@@ -142,6 +159,17 @@ amem ui
|
|
|
142
159
|
|
|
143
160
|
That opens `http://127.0.0.1:7843` on the **Setup** tab. It scans your home folder for git repos (skips `Library`, `node_modules`, `Downloads`, and similar noise). Check the ones you want, pick clients (Cursor, Claude Code, Windsurf, Continue, Aider, Zed, …), then **Start tracking selected**. Each pick is bound in `~/.amem` and gets the matching installer when available.
|
|
144
161
|
|
|
162
|
+
Prefer a desktop window instead of a browser tab (same localhost server, same privacy):
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# once per machine/checkout (downloads Electron — not included in npm i -g)
|
|
166
|
+
npm run app:setup
|
|
167
|
+
|
|
168
|
+
amem app
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`amem ui` keeps opening the browser; `amem app` opens Electron. Both talk to `127.0.0.1` only. If the UI server is already running, `amem app` attaches to it. Global installs: run `npm run app:setup` from the package directory (or clone), then `amem app`.
|
|
172
|
+
|
|
145
173
|
The header has a **Personal** switcher (cross-repo prefs) and **Lock / backup** chrome — lock status, last backup, and a daily local schedule. Memory shows the same lock/backup chips. The Setup tab includes a copyable **remember contract** for any MCP host (`amem recipe`).
|
|
146
174
|
|
|
147
175
|
Optional: check **Start amem ui when this computer logs in** so the localhost server comes back after a reboot:
|
|
@@ -156,7 +184,8 @@ Tabs after setup:
|
|
|
156
184
|
|
|
157
185
|
1. **Setup** — scan/select repos, platforms, login auto-start, bootstrap proposal
|
|
158
186
|
2. **Memory** — facts by file, scored drafts (approve / replace older / dismiss / reject noisy), edit/pin/delete, search, recent hits/misses
|
|
159
|
-
3. **
|
|
187
|
+
3. **Tasks** — per-project Kanban for deferred agent work (Backlog → Next → Doing → Blocked → Done). MCP: `amem_task_add` / `amem_task_update` / `amem_task_complete`. Open tasks appear in `amem_context`. Use Memory for durable facts; Tasks for “do later.”
|
|
188
|
+
4. **Stats** — estimated tokens saved per LLM, plus JSON / markdown / PDF export (proxies, not a bill)
|
|
160
189
|
|
|
161
190
|
Server-only (no browser open):
|
|
162
191
|
|
|
@@ -225,6 +254,8 @@ Memory is a small local graph in SQLite:
|
|
|
225
254
|
| **Claim** | A durable fact with file anchors (may be `active` or `superseded`; optional pin) |
|
|
226
255
|
| **Edge** | Links (claim → flow → component); `kind: "supersedes"` archives the target claim |
|
|
227
256
|
| **Draft** | Pending session / miss→learn proposals waiting for Memory approve |
|
|
257
|
+
| **Task** | Deferred work on the project Kanban (Backlog / Next / Doing / Blocked / Done) — not a durable fact |
|
|
258
|
+
| **Skill** | A reusable multi-step procedure, stored as a `SKILL.md` file (see below) |
|
|
228
259
|
| **Usage event** | Each `amem context` hit + token estimate |
|
|
229
260
|
|
|
230
261
|
Claims are the retrieval unit. Ranking combines:
|
|
@@ -252,6 +283,60 @@ Example claim:
|
|
|
252
283
|
|
|
253
284
|
---
|
|
254
285
|
|
|
286
|
+
## Skills (procedural memory)
|
|
287
|
+
|
|
288
|
+
Claims answer *what is true*. Skills answer *how we do this here* — a deploy sequence, a
|
|
289
|
+
migration dance, a debugging path someone already walked. They are too long to sit in every
|
|
290
|
+
prompt, so they load on demand.
|
|
291
|
+
|
|
292
|
+
Skills live as `SKILL.md` files under `~/.amem/skills/`, indexed in SQLite for ranking:
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
~/.amem/skills/deploy-staging/SKILL.md
|
|
296
|
+
~/.amem/skills/deploy-staging/references/runbook.md
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Progressive disclosure.** A context packet carries only names and descriptions. The agent
|
|
300
|
+
calls `amem_skill_view` to pull a body once it decides the procedure applies, so an unused
|
|
301
|
+
library of skills costs almost no tokens.
|
|
302
|
+
|
|
303
|
+
**The learning loop.** amem ships no model, so it never writes a skill itself. At session end
|
|
304
|
+
it looks for the shape of a hard-won procedure — enumerated steps or real commands, plus an
|
|
305
|
+
error it recovered from or a correction you gave. When several signals line up it queues one
|
|
306
|
+
suggestion, which reaches your agent as a nudge in the next context packet. The agent writes
|
|
307
|
+
the skill; you approve it. If a skill was loaded during a session that still went sideways,
|
|
308
|
+
amem queues a revision instead of a duplicate.
|
|
309
|
+
|
|
310
|
+
The bar is deliberately high, and a session can queue at most one suggestion.
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
amem skills list # index of what is stored
|
|
314
|
+
amem skills show deploy-staging # full body
|
|
315
|
+
amem skills new deploy-staging --desc "Deploy staging and verify health"
|
|
316
|
+
amem skills import ./some-skill # bring in an agentskills.io skill
|
|
317
|
+
amem skills drafts # pending suggestions and staged writes
|
|
318
|
+
amem skills approve <draft-id>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Skills are also a Skills tab in `amem ui`.
|
|
322
|
+
|
|
323
|
+
**Safety.** Skills are instructions an agent will follow, so content is scanned for
|
|
324
|
+
credentials and prompt-injection patterns before any write. Three policy keys control them:
|
|
325
|
+
|
|
326
|
+
| Key | Default | Effect |
|
|
327
|
+
| --- | --- | --- |
|
|
328
|
+
| `skills_enabled` | `true` | Master switch for storage, ranking, and injection |
|
|
329
|
+
| `skill_write_approval` | `false` | Stage agent writes for review instead of writing to disk |
|
|
330
|
+
| `skill_capture` | `true` | Allow session-end skill suggestions |
|
|
331
|
+
|
|
332
|
+
An unreadable `policy.toml` forces `skill_write_approval` on. `amem doctor --attest` reports
|
|
333
|
+
every installed skill with a content hash, so you can diff what agents are being told to do.
|
|
334
|
+
|
|
335
|
+
> Backups currently copy the database only — `~/.amem/skills/` is not included yet. Keep
|
|
336
|
+
> skills you care about in version control until that lands.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
255
340
|
## Token savings (estimates)
|
|
256
341
|
|
|
257
342
|
Every `amem context` logs a usage event. The UI **Stats** tab breaks this down by platform (`cursor`, `claude`, …).
|
|
@@ -352,6 +437,9 @@ MCP tools (stdio or HTTP):
|
|
|
352
437
|
| `amem_context` | Ranked memory packet for the current question |
|
|
353
438
|
| `amem_remember` | Store a durable fact after an outcome |
|
|
354
439
|
| `amem_recipe` | Generic read-then-write contract (any MCP host) |
|
|
440
|
+
| `amem_skill_list` | Cheap index of stored procedures (names + descriptions only) |
|
|
441
|
+
| `amem_skill_view` | Load one skill body, after the index says it applies |
|
|
442
|
+
| `amem_skill_save` | Store a multi-step procedure you just worked out |
|
|
355
443
|
| `amem_repos` | What is monitored (git repos + named workspaces) |
|
|
356
444
|
| `amem_stats` | Lookup time, estimated tokens/ms saved, hit rate |
|
|
357
445
|
| `amem_graph` | Claims / components / flows stored for a workspace or repo |
|
|
@@ -372,6 +460,8 @@ amem doctor [--attest] [--json]
|
|
|
372
460
|
amem context "<query>" [--workspace <name>] [--platform …]
|
|
373
461
|
amem remember "<text>" [--workspace <name>] [--kind …] [--anchor <path>]
|
|
374
462
|
amem recipe [--json]
|
|
463
|
+
amem skills list|show <name>|new <name> [--desc <text>]|rm <name>|sync|import <path>
|
|
464
|
+
amem skills drafts|approve <draft-id>|dismiss <draft-id>
|
|
375
465
|
amem mcp [--print-config] [--workspace <name>]
|
|
376
466
|
amem propose validate|diff|apply <file.json>
|
|
377
467
|
amem export [--out <file.json>]
|
|
@@ -388,6 +478,7 @@ amem usage export [--format json|md|pdf] [--days 30] [--scope current|all] [--ou
|
|
|
388
478
|
amem license status|apply|activate|clear|issue|keys
|
|
389
479
|
amem embed status|use hash|use ngram|reindex
|
|
390
480
|
amem ui [--port 7843] [--no-open]
|
|
481
|
+
amem app [--port 7843]
|
|
391
482
|
amem service install|uninstall|status
|
|
392
483
|
```
|
|
393
484
|
|
|
@@ -399,11 +490,13 @@ amem service install|uninstall|status
|
|
|
399
490
|
| `context` | Retrieve a Markdown packet; log usage |
|
|
400
491
|
| `remember` | Store one local fact |
|
|
401
492
|
| `mcp` | Stdio MCP tools; HTTP MCP at `http://127.0.0.1:7843/mcp` while UI runs |
|
|
493
|
+
| `skills` | Manage procedural memory (`list`, `show`, `new`, `import`, `drafts`, `approve`) |
|
|
402
494
|
| `propose diff` | Preview claim/component/flow changes before apply |
|
|
403
495
|
| `propose apply` | Upsert structured memory locally |
|
|
404
496
|
| `lock` / `unlock` | Optional AES-256-GCM encrypt-at-rest for `graph.db` |
|
|
405
497
|
| `backup` | Local snapshot (optionally encrypted); `schedule` for daily timer |
|
|
406
|
-
| `ui` | Setup wizard + Memory + Stats
|
|
498
|
+
| `ui` | Setup wizard + Memory + Stats in the browser (localhost) |
|
|
499
|
+
| `app` | Same UI in an Electron window (`npm run app:setup` once) |
|
|
407
500
|
| `service` | Login item so `amem ui` starts after reboot |
|
|
408
501
|
| `doctor --attest` | Privacy/policy attestation for IT tickets |
|
|
409
502
|
| `export` / `wipe` | Personal backup or delete (still local) |
|
|
@@ -490,7 +583,8 @@ IT / DevEx can govern the **fleet**: approved install, policy, attestation, offb
|
|
|
490
583
|
|
|
491
584
|
Hard guarantees (not configurable away):
|
|
492
585
|
|
|
493
|
-
- No telemetry
|
|
586
|
+
- No memory telemetry or claim upload — `~/.amem` stays local
|
|
587
|
+
- Optional anonymous npm install ping only (opt out: `AMEM_TELEMETRY_DISABLED=1`)
|
|
494
588
|
- UI binds to loopback only (`127.0.0.1`)
|
|
495
589
|
- Memory stays under `~/.amem` (mode `0700`)
|
|
496
590
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/desktop/main.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin Electron shell: loads the existing amem localhost UI.
|
|
3
|
+
* No Node integration in the renderer — API stays on 127.0.0.1.
|
|
4
|
+
*
|
|
5
|
+
* macOS note: BrowserWindow `icon` does not change the Dock for an unpackaged
|
|
6
|
+
* Electron.app. We must call app.dock.setIcon() with a PNG (icns often loads empty).
|
|
7
|
+
*/
|
|
8
|
+
const { app, BrowserWindow, nativeImage } = require("electron");
|
|
9
|
+
const fs = require("node:fs");
|
|
10
|
+
const path = require("node:path");
|
|
11
|
+
|
|
12
|
+
function uiUrl() {
|
|
13
|
+
const fromEnv = String(process.env.AMEM_UI_URL || "").trim();
|
|
14
|
+
if (fromEnv) return fromEnv;
|
|
15
|
+
const arg = process.argv.find((a) => /^https?:\/\//.test(a));
|
|
16
|
+
return arg || "http://127.0.0.1:7843/";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function iconsDir() {
|
|
20
|
+
return path.join(__dirname, "icons");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Prefer PNG for Dock — nativeImage + .icns is unreliable for unpackaged Electron. */
|
|
24
|
+
function loadAppIcon() {
|
|
25
|
+
const dir = iconsDir();
|
|
26
|
+
const candidates =
|
|
27
|
+
process.platform === "darwin"
|
|
28
|
+
? ["icon-1024.png", "icon.png", "icon.icns"]
|
|
29
|
+
: process.platform === "win32"
|
|
30
|
+
? ["icon.ico", "icon.png", "icon-1024.png"]
|
|
31
|
+
: ["icon.png", "icon-1024.png"];
|
|
32
|
+
|
|
33
|
+
for (const name of candidates) {
|
|
34
|
+
const iconPath = path.join(dir, name);
|
|
35
|
+
if (!fs.existsSync(iconPath)) continue;
|
|
36
|
+
let image = nativeImage.createFromPath(iconPath);
|
|
37
|
+
if (image.isEmpty()) continue;
|
|
38
|
+
// Dock looks sharper with a mid-size bitmap.
|
|
39
|
+
const { width } = image.getSize();
|
|
40
|
+
if (width > 512) {
|
|
41
|
+
image = image.resize({ width: 512, height: 512, quality: "best" });
|
|
42
|
+
}
|
|
43
|
+
return image;
|
|
44
|
+
}
|
|
45
|
+
console.warn("[amem-desktop] no usable icon in", dir);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function applyDockIcon(icon) {
|
|
50
|
+
if (!icon || process.platform !== "darwin" || !app.dock) return;
|
|
51
|
+
try {
|
|
52
|
+
app.dock.setIcon(icon);
|
|
53
|
+
app.dock.show();
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.warn("[amem-desktop] dock.setIcon failed:", err instanceof Error ? err.message : err);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createWindow(icon) {
|
|
60
|
+
const win = new BrowserWindow({
|
|
61
|
+
width: 1280,
|
|
62
|
+
height: 860,
|
|
63
|
+
minWidth: 880,
|
|
64
|
+
minHeight: 560,
|
|
65
|
+
title: "amem",
|
|
66
|
+
show: false,
|
|
67
|
+
backgroundColor: "#0b0f12",
|
|
68
|
+
...(icon ? { icon } : {}),
|
|
69
|
+
webPreferences: {
|
|
70
|
+
preload: path.join(__dirname, "preload.js"),
|
|
71
|
+
contextIsolation: true,
|
|
72
|
+
nodeIntegration: false,
|
|
73
|
+
sandbox: true,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
win.once("ready-to-show", () => {
|
|
78
|
+
applyDockIcon(icon);
|
|
79
|
+
win.show();
|
|
80
|
+
});
|
|
81
|
+
win.loadURL(uiUrl()).catch((err) => {
|
|
82
|
+
console.error("[amem-desktop] failed to load UI:", err instanceof Error ? err.message : err);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
app.setName("amem");
|
|
87
|
+
|
|
88
|
+
app.whenReady().then(() => {
|
|
89
|
+
const icon = loadAppIcon();
|
|
90
|
+
applyDockIcon(icon);
|
|
91
|
+
createWindow(icon);
|
|
92
|
+
app.on("activate", () => {
|
|
93
|
+
applyDockIcon(icon);
|
|
94
|
+
if (BrowserWindow.getAllWindows().length === 0) createWindow(icon);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
app.on("window-all-closed", () => {
|
|
99
|
+
app.quit();
|
|
100
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "amem-desktop",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Electron shell for local amem Brain UI (localhost only).",
|
|
6
|
+
"main": "main.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "electron ."
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"electron": "^33.2.1"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Ensure the Electron binary finished downloading after npm install
|
|
4
|
+
* (some environments skip lifecycle scripts / botch macOS framework unzip).
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, rmSync, writeFileSync, statSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
const desktopRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
13
|
+
const electronPkg = join(desktopRoot, "node_modules", "electron");
|
|
14
|
+
const pathTxt = join(electronPkg, "path.txt");
|
|
15
|
+
const distDir = join(electronPkg, "dist");
|
|
16
|
+
|
|
17
|
+
if (!existsSync(join(electronPkg, "package.json"))) {
|
|
18
|
+
console.error("electron is not installed under desktop/. Run: npm install --prefix desktop");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function platformRelative() {
|
|
23
|
+
if (process.platform === "darwin") return "Electron.app/Contents/MacOS/Electron";
|
|
24
|
+
if (process.platform === "win32") return "electron.exe";
|
|
25
|
+
return "electron";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function binaryPath() {
|
|
29
|
+
return join(distDir, platformRelative());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function frameworkOk() {
|
|
33
|
+
if (process.platform !== "darwin") return true;
|
|
34
|
+
return existsSync(
|
|
35
|
+
join(distDir, "Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework"),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function looksComplete() {
|
|
40
|
+
const bin = binaryPath();
|
|
41
|
+
if (!existsSync(bin)) return false;
|
|
42
|
+
if (!frameworkOk()) return false;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function requireBin() {
|
|
47
|
+
try {
|
|
48
|
+
const require = createRequire(pathToFileURL(join(desktopRoot, "package.json")).href);
|
|
49
|
+
const bin = require("electron");
|
|
50
|
+
return typeof bin === "string" && existsSync(bin) ? bin : null;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function downloadZip() {
|
|
57
|
+
const require = createRequire(join(electronPkg, "package.json"));
|
|
58
|
+
const { downloadArtifact } = require("@electron/get");
|
|
59
|
+
const { version } = require(join(electronPkg, "package.json"));
|
|
60
|
+
const checksums = require(join(electronPkg, "checksums.json"));
|
|
61
|
+
return downloadArtifact({
|
|
62
|
+
version,
|
|
63
|
+
artifactName: "electron",
|
|
64
|
+
force: true,
|
|
65
|
+
platform: process.platform,
|
|
66
|
+
arch: process.arch,
|
|
67
|
+
checksums,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function ensure() {
|
|
72
|
+
if (looksComplete()) {
|
|
73
|
+
if (!existsSync(pathTxt)) writeFileSync(pathTxt, platformRelative());
|
|
74
|
+
console.log(`Electron ready: ${requireBin() || binaryPath()}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log("Downloading Electron binary…");
|
|
79
|
+
rmSync(distDir, { recursive: true, force: true });
|
|
80
|
+
try {
|
|
81
|
+
rmSync(pathTxt, { force: true });
|
|
82
|
+
} catch {
|
|
83
|
+
/* ignore */
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const zipPath = await downloadZip();
|
|
87
|
+
mkdirSync(distDir, { recursive: true });
|
|
88
|
+
|
|
89
|
+
// System unzip preserves macOS framework symlinks; extract-zip often does not.
|
|
90
|
+
const unzip = spawnSync("unzip", ["-o", "-q", zipPath, "-d", distDir], { stdio: "inherit" });
|
|
91
|
+
if (unzip.status !== 0) {
|
|
92
|
+
// Fallback for Windows / machines without unzip
|
|
93
|
+
const extract = requireFromElectron("extract-zip");
|
|
94
|
+
await extract(zipPath, { dir: distDir });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
writeFileSync(pathTxt, platformRelative());
|
|
98
|
+
// Match electron/install.js version marker when present
|
|
99
|
+
try {
|
|
100
|
+
const { version } = createRequire(join(electronPkg, "package.json"))("./package.json");
|
|
101
|
+
writeFileSync(join(distDir, "version"), version);
|
|
102
|
+
} catch {
|
|
103
|
+
/* ignore */
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!looksComplete()) {
|
|
107
|
+
console.error("Electron binary still incomplete after install.");
|
|
108
|
+
console.error("Try: rm -rf desktop/node_modules && npm run app:setup");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`Electron ready: ${requireBin() || binaryPath()}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function requireFromElectron(id) {
|
|
116
|
+
return createRequire(join(electronPkg, "package.json"))(id);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
ensure().catch((err) => {
|
|
120
|
+
console.error(err instanceof Error ? err.stack : err);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
});
|