@companion-ai/feynman 0.2.5 → 0.2.7
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/.feynman/settings.json +0 -1
- package/README.md +3 -1
- package/dist/cli.js +64 -0
- package/dist/pi/package-presets.js +56 -0
- package/dist/pi/settings.js +7 -0
- package/extensions/research-tools/shared.ts +1 -8
- package/metadata/commands.mjs +3 -1
- package/package.json +1 -1
- package/scripts/patch-embedded-pi.mjs +106 -2
package/.feynman/settings.json
CHANGED
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ Four bundled research agents, dispatched automatically or via subagent commands.
|
|
|
61
61
|
- **Docker** — isolated container execution for safe experiments on your machine
|
|
62
62
|
- **[Agent Computer](https://agentcomputer.ai)** — secure cloud execution for long-running research and GPU workloads
|
|
63
63
|
- **Web search** — Gemini or Perplexity, zero-config default via signed-in Chromium
|
|
64
|
-
- **Session search** — indexed recall across prior research sessions
|
|
64
|
+
- **Session search** — optional indexed recall across prior research sessions
|
|
65
65
|
- **Preview** — browser and PDF export of generated artifacts
|
|
66
66
|
|
|
67
67
|
---
|
|
@@ -76,6 +76,8 @@ feynman status # current config summary
|
|
|
76
76
|
feynman model login [provider] # model auth
|
|
77
77
|
feynman model set <provider/model> # set default model
|
|
78
78
|
feynman alpha login # alphaXiv auth
|
|
79
|
+
feynman packages list # core vs optional packages
|
|
80
|
+
feynman packages install memory # opt into heavier packages on demand
|
|
79
81
|
feynman search status # web search config
|
|
80
82
|
```
|
|
81
83
|
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { AuthStorage, DefaultPackageManager, ModelRegistry, SettingsManager } fr
|
|
|
8
8
|
import { syncBundledAssets } from "./bootstrap/sync.js";
|
|
9
9
|
import { ensureFeynmanHome, getDefaultSessionDir, getFeynmanAgentDir, getFeynmanHome } from "./config/paths.js";
|
|
10
10
|
import { launchPiChat } from "./pi/launch.js";
|
|
11
|
+
import { CORE_PACKAGE_SOURCES, getOptionalPackagePresetSources, listOptionalPackagePresets } from "./pi/package-presets.js";
|
|
11
12
|
import { normalizeFeynmanSettings, normalizeThinkingLevel, parseModelSpec } from "./pi/settings.js";
|
|
12
13
|
import { loginModelProvider, logoutModelProvider, printModelList, setDefaultModelSpec, } from "./model/commands.js";
|
|
13
14
|
import { printSearchStatus } from "./search/commands.js";
|
|
@@ -125,6 +126,65 @@ async function handleUpdateCommand(workingDir, feynmanAgentDir, source) {
|
|
|
125
126
|
await settingsManager.flush();
|
|
126
127
|
console.log("All packages up to date.");
|
|
127
128
|
}
|
|
129
|
+
async function handlePackagesCommand(subcommand, args, workingDir, feynmanAgentDir) {
|
|
130
|
+
const settingsManager = SettingsManager.create(workingDir, feynmanAgentDir);
|
|
131
|
+
const configuredSources = new Set(settingsManager
|
|
132
|
+
.getPackages()
|
|
133
|
+
.map((entry) => (typeof entry === "string" ? entry : entry.source))
|
|
134
|
+
.filter((entry) => typeof entry === "string"));
|
|
135
|
+
if (!subcommand || subcommand === "list") {
|
|
136
|
+
printPanel("Feynman Packages", [
|
|
137
|
+
"Core packages are installed by default to keep first-run setup fast.",
|
|
138
|
+
]);
|
|
139
|
+
printSection("Core");
|
|
140
|
+
for (const source of CORE_PACKAGE_SOURCES) {
|
|
141
|
+
printInfo(source);
|
|
142
|
+
}
|
|
143
|
+
printSection("Optional");
|
|
144
|
+
for (const preset of listOptionalPackagePresets()) {
|
|
145
|
+
const installed = preset.sources.every((source) => configuredSources.has(source));
|
|
146
|
+
printInfo(`${preset.name}${installed ? " (installed)" : ""} ${preset.description}`);
|
|
147
|
+
}
|
|
148
|
+
printInfo("Install with: feynman packages install <preset>");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (subcommand !== "install") {
|
|
152
|
+
throw new Error(`Unknown packages command: ${subcommand}`);
|
|
153
|
+
}
|
|
154
|
+
const target = args[0];
|
|
155
|
+
if (!target) {
|
|
156
|
+
throw new Error("Usage: feynman packages install <generative-ui|memory|session-search|all-extras>");
|
|
157
|
+
}
|
|
158
|
+
const sources = getOptionalPackagePresetSources(target);
|
|
159
|
+
if (!sources) {
|
|
160
|
+
throw new Error(`Unknown package preset: ${target}`);
|
|
161
|
+
}
|
|
162
|
+
const packageManager = new DefaultPackageManager({
|
|
163
|
+
cwd: workingDir,
|
|
164
|
+
agentDir: feynmanAgentDir,
|
|
165
|
+
settingsManager,
|
|
166
|
+
});
|
|
167
|
+
packageManager.setProgressCallback((event) => {
|
|
168
|
+
if (event.type === "start") {
|
|
169
|
+
console.log(`Installing ${event.source}...`);
|
|
170
|
+
}
|
|
171
|
+
else if (event.type === "complete") {
|
|
172
|
+
console.log(`Installed ${event.source}`);
|
|
173
|
+
}
|
|
174
|
+
else if (event.type === "error") {
|
|
175
|
+
console.error(`Failed to install ${event.source}: ${event.message ?? "unknown error"}`);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
for (const source of sources) {
|
|
179
|
+
if (configuredSources.has(source)) {
|
|
180
|
+
console.log(`${source} already installed`);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
await packageManager.install(source);
|
|
184
|
+
}
|
|
185
|
+
await settingsManager.flush();
|
|
186
|
+
console.log("Optional packages installed.");
|
|
187
|
+
}
|
|
128
188
|
function handleSearchCommand(subcommand) {
|
|
129
189
|
if (!subcommand || subcommand === "status") {
|
|
130
190
|
printSearchStatus();
|
|
@@ -267,6 +327,10 @@ export async function main() {
|
|
|
267
327
|
handleSearchCommand(rest[0]);
|
|
268
328
|
return;
|
|
269
329
|
}
|
|
330
|
+
if (command === "packages") {
|
|
331
|
+
await handlePackagesCommand(rest[0], rest.slice(1), workingDir, feynmanAgentDir);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
270
334
|
if (command === "update") {
|
|
271
335
|
await handleUpdateCommand(workingDir, feynmanAgentDir, rest[0]);
|
|
272
336
|
return;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const CORE_PACKAGE_SOURCES = [
|
|
2
|
+
"npm:pi-subagents",
|
|
3
|
+
"npm:pi-btw",
|
|
4
|
+
"npm:pi-docparser",
|
|
5
|
+
"npm:pi-web-access",
|
|
6
|
+
"npm:pi-markdown-preview",
|
|
7
|
+
"npm:@walterra/pi-charts",
|
|
8
|
+
"npm:pi-mermaid",
|
|
9
|
+
"npm:@aliou/pi-processes",
|
|
10
|
+
"npm:pi-zotero",
|
|
11
|
+
"npm:@kaiserlich-dev/pi-session-search",
|
|
12
|
+
"npm:pi-schedule-prompt",
|
|
13
|
+
"npm:@samfp/pi-memory",
|
|
14
|
+
"npm:@tmustier/pi-ralph-wiggum",
|
|
15
|
+
];
|
|
16
|
+
export const OPTIONAL_PACKAGE_PRESETS = {
|
|
17
|
+
"generative-ui": {
|
|
18
|
+
description: "Interactive Glimpse UI widgets.",
|
|
19
|
+
sources: ["npm:pi-generative-ui"],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const LEGACY_DEFAULT_PACKAGE_SOURCES = [
|
|
23
|
+
...CORE_PACKAGE_SOURCES,
|
|
24
|
+
"npm:pi-generative-ui",
|
|
25
|
+
];
|
|
26
|
+
function arraysMatchAsSets(left, right) {
|
|
27
|
+
if (left.length !== right.length) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const rightSet = new Set(right);
|
|
31
|
+
return left.every((entry) => rightSet.has(entry));
|
|
32
|
+
}
|
|
33
|
+
export function shouldPruneLegacyDefaultPackages(packages) {
|
|
34
|
+
if (!Array.isArray(packages)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (packages.some((entry) => typeof entry !== "string")) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return arraysMatchAsSets(packages, LEGACY_DEFAULT_PACKAGE_SOURCES);
|
|
41
|
+
}
|
|
42
|
+
export function getOptionalPackagePresetSources(name) {
|
|
43
|
+
const normalized = name.trim().toLowerCase();
|
|
44
|
+
if (normalized === "ui") {
|
|
45
|
+
return [...OPTIONAL_PACKAGE_PRESETS["generative-ui"].sources];
|
|
46
|
+
}
|
|
47
|
+
const preset = OPTIONAL_PACKAGE_PRESETS[normalized];
|
|
48
|
+
return preset ? [...preset.sources] : undefined;
|
|
49
|
+
}
|
|
50
|
+
export function listOptionalPackagePresets() {
|
|
51
|
+
return Object.entries(OPTIONAL_PACKAGE_PRESETS).map(([name, preset]) => ({
|
|
52
|
+
name: name,
|
|
53
|
+
description: preset.description,
|
|
54
|
+
sources: [...preset.sources],
|
|
55
|
+
}));
|
|
56
|
+
}
|
package/dist/pi/settings.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import { CORE_PACKAGE_SOURCES, shouldPruneLegacyDefaultPackages } from "./package-presets.js";
|
|
4
5
|
export function parseModelSpec(spec, modelRegistry) {
|
|
5
6
|
const trimmed = spec.trim();
|
|
6
7
|
const separator = trimmed.includes(":") ? ":" : trimmed.includes("/") ? "/" : null;
|
|
@@ -83,6 +84,12 @@ export function normalizeFeynmanSettings(settingsPath, bundledSettingsPath, defa
|
|
|
83
84
|
settings.theme = "feynman";
|
|
84
85
|
settings.quietStartup = true;
|
|
85
86
|
settings.collapseChangelog = true;
|
|
87
|
+
if (!Array.isArray(settings.packages) || settings.packages.length === 0) {
|
|
88
|
+
settings.packages = [...CORE_PACKAGE_SOURCES];
|
|
89
|
+
}
|
|
90
|
+
else if (shouldPruneLegacyDefaultPackages(settings.packages)) {
|
|
91
|
+
settings.packages = [...CORE_PACKAGE_SOURCES];
|
|
92
|
+
}
|
|
86
93
|
const authStorage = AuthStorage.create(authPath);
|
|
87
94
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
88
95
|
const availableModels = modelRegistry.getAvailable().map((model) => ({
|
|
@@ -14,14 +14,7 @@ export const FEYNMAN_VERSION = (() => {
|
|
|
14
14
|
}
|
|
15
15
|
})();
|
|
16
16
|
|
|
17
|
-
export
|
|
18
|
-
"███████╗███████╗██╗ ██╗███╗ ██╗███╗ ███╗ █████╗ ███╗ ██╗",
|
|
19
|
-
"██╔════╝██╔════╝╚██╗ ██╔╝████╗ ██║████╗ ████║██╔══██╗████╗ ██║",
|
|
20
|
-
"█████╗ █████╗ ╚████╔╝ ██╔██╗ ██║██╔████╔██║███████║██╔██╗ ██║",
|
|
21
|
-
"██╔══╝ ██╔══╝ ╚██╔╝ ██║╚██╗██║██║╚██╔╝██║██╔══██║██║╚██╗██║",
|
|
22
|
-
"██║ ███████╗ ██║ ██║ ╚████║██║ ╚═╝ ██║██║ ██║██║ ╚████║",
|
|
23
|
-
"╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝",
|
|
24
|
-
];
|
|
17
|
+
export { FEYNMAN_ASCII_LOGO as FEYNMAN_AGENT_LOGO } from "../../logo.mjs";
|
|
25
18
|
|
|
26
19
|
export const FEYNMAN_RESEARCH_TOOLS = [
|
|
27
20
|
"alpha_search",
|
package/metadata/commands.mjs
CHANGED
|
@@ -98,6 +98,8 @@ export const cliCommandSections = [
|
|
|
98
98
|
{
|
|
99
99
|
title: "Utilities",
|
|
100
100
|
commands: [
|
|
101
|
+
{ usage: "feynman packages list", description: "Show core and optional Pi package presets." },
|
|
102
|
+
{ usage: "feynman packages install <preset>", description: "Install optional package presets on demand." },
|
|
101
103
|
{ usage: "feynman search status", description: "Show Pi web-access status and config path." },
|
|
102
104
|
{ usage: "feynman update [package]", description: "Update installed packages, or a specific package." },
|
|
103
105
|
],
|
|
@@ -118,7 +120,7 @@ export const legacyFlags = [
|
|
|
118
120
|
{ usage: "--setup-preview", description: "Alias for `feynman setup preview`." },
|
|
119
121
|
];
|
|
120
122
|
|
|
121
|
-
export const topLevelCommandNames = ["alpha", "chat", "doctor", "help", "model", "search", "setup", "status", "update"];
|
|
123
|
+
export const topLevelCommandNames = ["alpha", "chat", "doctor", "help", "model", "packages", "search", "setup", "status", "update"];
|
|
122
124
|
|
|
123
125
|
export function formatSlashUsage(command) {
|
|
124
126
|
return `/${command.name}${command.args ? ` ${command.args}` : ""}`;
|
package/package.json
CHANGED
|
@@ -2,9 +2,11 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { FEYNMAN_ASCII_LOGO_HTML } from "../logo.mjs";
|
|
5
6
|
|
|
6
7
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
const appRoot = resolve(here, "..");
|
|
9
|
+
const isGlobalInstall = process.env.npm_config_global === "true" || process.env.npm_config_location === "global";
|
|
8
10
|
|
|
9
11
|
function findNodeModules() {
|
|
10
12
|
let dir = appRoot;
|
|
@@ -53,7 +55,88 @@ const settingsPath = resolve(appRoot, ".feynman", "settings.json");
|
|
|
53
55
|
const workspaceDir = resolve(appRoot, ".feynman", "npm");
|
|
54
56
|
const workspacePackageJsonPath = resolve(workspaceDir, "package.json");
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
function resolveExecutable(name, fallbackPaths = []) {
|
|
59
|
+
for (const candidate of fallbackPaths) {
|
|
60
|
+
if (existsSync(candidate)) return candidate;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result = spawnSync("sh", ["-lc", `command -v ${name}`], {
|
|
64
|
+
encoding: "utf8",
|
|
65
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
66
|
+
});
|
|
67
|
+
if (result.status === 0) {
|
|
68
|
+
const resolved = result.stdout.trim();
|
|
69
|
+
if (resolved) return resolved;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function ensurePackageWorkspace() {
|
|
75
|
+
if (!existsSync(settingsPath)) return;
|
|
76
|
+
|
|
77
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
78
|
+
const packageSpecs = Array.isArray(settings.packages)
|
|
79
|
+
? settings.packages
|
|
80
|
+
.filter((v) => typeof v === "string" && v.startsWith("npm:"))
|
|
81
|
+
.map((v) => v.slice(4))
|
|
82
|
+
: [];
|
|
83
|
+
|
|
84
|
+
if (packageSpecs.length === 0) return;
|
|
85
|
+
if (existsSync(resolve(workspaceRoot, packageSpecs[0]))) return;
|
|
86
|
+
|
|
87
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
88
|
+
writeFileSync(
|
|
89
|
+
workspacePackageJsonPath,
|
|
90
|
+
JSON.stringify({ name: "feynman-packages", private: true }, null, 2) + "\n",
|
|
91
|
+
"utf8",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
95
|
+
let frame = 0;
|
|
96
|
+
const start = Date.now();
|
|
97
|
+
const spinner = setInterval(() => {
|
|
98
|
+
const elapsed = Math.round((Date.now() - start) / 1000);
|
|
99
|
+
process.stderr.write(`\r${frames[frame++ % frames.length]} setting up feynman... ${elapsed}s`);
|
|
100
|
+
}, 80);
|
|
101
|
+
|
|
102
|
+
const result = spawnSync("npm", ["install", "--prefer-offline", "--no-audit", "--no-fund", "--loglevel", "error", "--prefix", workspaceDir, ...packageSpecs], {
|
|
103
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
104
|
+
timeout: 300000,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
clearInterval(spinner);
|
|
108
|
+
const elapsed = Math.round((Date.now() - start) / 1000);
|
|
109
|
+
|
|
110
|
+
if (result.status !== 0) {
|
|
111
|
+
process.stderr.write(`\r✗ setup failed (${elapsed}s)\n`);
|
|
112
|
+
if (result.stderr?.length) process.stderr.write(result.stderr);
|
|
113
|
+
} else {
|
|
114
|
+
process.stderr.write(`\r✓ feynman ready (${elapsed}s)\n`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ensurePackageWorkspace();
|
|
119
|
+
|
|
120
|
+
function ensurePandoc() {
|
|
121
|
+
if (!isGlobalInstall) return;
|
|
122
|
+
if (process.platform !== "darwin") return;
|
|
123
|
+
if (process.env.FEYNMAN_SKIP_PANDOC_INSTALL === "1") return;
|
|
124
|
+
if (resolveExecutable("pandoc", ["/opt/homebrew/bin/pandoc", "/usr/local/bin/pandoc"])) return;
|
|
125
|
+
|
|
126
|
+
const brewPath = resolveExecutable("brew", ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"]);
|
|
127
|
+
if (!brewPath) return;
|
|
128
|
+
|
|
129
|
+
console.log("[feynman] installing pandoc...");
|
|
130
|
+
const result = spawnSync(brewPath, ["install", "pandoc"], {
|
|
131
|
+
stdio: "inherit",
|
|
132
|
+
timeout: 300000,
|
|
133
|
+
});
|
|
134
|
+
if (result.status !== 0) {
|
|
135
|
+
console.warn("[feynman] warning: pandoc install failed, run `feynman --setup-preview` later");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
ensurePandoc();
|
|
57
140
|
|
|
58
141
|
if (existsSync(packageJsonPath)) {
|
|
59
142
|
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
@@ -282,12 +365,33 @@ if (oauthPagePath && existsSync(oauthPagePath)) {
|
|
|
282
365
|
let source = readFileSync(oauthPagePath, "utf8");
|
|
283
366
|
const piLogo = 'const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;';
|
|
284
367
|
if (source.includes(piLogo)) {
|
|
285
|
-
const feynmanLogo =
|
|
368
|
+
const feynmanLogo = `const LOGO_SVG = \`${FEYNMAN_ASCII_LOGO_HTML}\`;`;
|
|
286
369
|
source = source.replace(piLogo, feynmanLogo);
|
|
287
370
|
writeFileSync(oauthPagePath, source, "utf8");
|
|
288
371
|
}
|
|
289
372
|
}
|
|
290
373
|
|
|
374
|
+
const alphaHubAuthPath = findPackageRoot("@companion-ai/alpha-hub")
|
|
375
|
+
? resolve(findPackageRoot("@companion-ai/alpha-hub"), "src", "lib", "auth.js")
|
|
376
|
+
: null;
|
|
377
|
+
|
|
378
|
+
if (alphaHubAuthPath && existsSync(alphaHubAuthPath)) {
|
|
379
|
+
let source = readFileSync(alphaHubAuthPath, "utf8");
|
|
380
|
+
const callbackStyle = `style="font-family:system-ui,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:80vh;background:#050a08;color:#f0f5f2"`;
|
|
381
|
+
const logoHtml = FEYNMAN_ASCII_LOGO_HTML.replace('color:#10b981', 'color:#34d399');
|
|
382
|
+
const successPage = `<html><body ${callbackStyle}>${logoHtml}<h2 style="color:#34d399;margin-top:24px">Logged in</h2><p style="color:#8aaa9a">You can close this tab.</p></body></html>`;
|
|
383
|
+
const errorPage = `<html><body ${callbackStyle}>${logoHtml}<h2 style="color:#ef4444;margin-top:24px">Login failed</h2><p style="color:#8aaa9a">You can close this tab.</p></body></html>`;
|
|
384
|
+
const oldSuccess = `'<html><body><h2>Logged in to Alpha Hub</h2><p>You can close this tab.</p></body></html>'`;
|
|
385
|
+
const oldError = `'<html><body><h2>Login failed</h2><p>You can close this tab.</p></body></html>'`;
|
|
386
|
+
if (source.includes(oldSuccess)) {
|
|
387
|
+
source = source.replace(oldSuccess, `'${successPage}'`);
|
|
388
|
+
}
|
|
389
|
+
if (source.includes(oldError)) {
|
|
390
|
+
source = source.replace(oldError, `'${errorPage}'`);
|
|
391
|
+
}
|
|
392
|
+
writeFileSync(alphaHubAuthPath, source, "utf8");
|
|
393
|
+
}
|
|
394
|
+
|
|
291
395
|
if (existsSync(piMemoryPath)) {
|
|
292
396
|
let source = readFileSync(piMemoryPath, "utf8");
|
|
293
397
|
const memoryOriginal = 'const MEMORY_DIR = join(homedir(), ".pi", "memory");';
|