geneseed 3.10.0 → 3.11.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/CHANGELOG.md +57 -0
- package/DESIGN.md +1 -1
- package/README.md +26 -44
- package/SHIPPED.md +2 -2
- package/adapters/bob/README.md +1 -1
- package/adapters/claude-code/README.md +17 -12
- package/adapters/openclaude/README.md +1 -1
- package/adapters/opencode/README.md +4 -4
- package/bin/build-driver.mjs +2 -0
- package/bin/geneseed-cli.mjs +17 -0
- package/bin/geneseed-hook.mjs +2 -0
- package/docs/README.md +42 -0
- package/docs/_groups.json +18 -0
- package/docs/concepts/agents.md +44 -0
- package/docs/concepts/collaboration.md +62 -0
- package/docs/concepts/exclusions.md +55 -0
- package/docs/concepts/footprint.md +52 -0
- package/docs/concepts/foreman-mode.md +69 -0
- package/docs/concepts/hooks.md +101 -0
- package/docs/concepts/hosts.md +55 -0
- package/docs/concepts/lsp.md +50 -0
- package/docs/{web → concepts}/memory.md +2 -2
- package/docs/{web → concepts}/notebook.md +2 -2
- package/docs/concepts/rules.md +52 -0
- package/docs/concepts/skills.md +49 -0
- package/docs/concepts/themes.md +50 -0
- package/docs/extending.md +27 -21
- package/docs/guides/autostart.md +70 -0
- package/docs/guides/choose-your-setup.md +120 -0
- package/docs/guides/headless.md +22 -0
- package/docs/guides/install.md +161 -0
- package/docs/guides/mcp.md +195 -0
- package/docs/guides/migrate.md +41 -0
- package/docs/guides/project-context.md +87 -0
- package/docs/guides/run-anywhere.md +45 -0
- package/docs/guides/uninstall.md +51 -0
- package/docs/guides/upgrade.md +65 -0
- package/docs/guides/verify.md +65 -0
- package/docs/guides/wiki.md +69 -0
- package/docs/guides/worktree.md +47 -0
- package/docs/reference/about.md +7 -0
- package/docs/reference/cli.md +7 -0
- package/docs/reference/environment.md +99 -0
- package/docs/reference/glossary.md +33 -0
- package/docs/reference/opencode-plugins.md +131 -0
- package/docs/reference/troubleshooting.md +350 -0
- package/docs/understand/a-day.md +77 -0
- package/docs/understand/enforced-vs-asked.md +64 -0
- package/docs/understand/harness.md +36 -0
- package/docs/understand/on-your-machine.md +76 -0
- package/docs/web-ui.md +1 -1
- package/harness.config.json +1 -1
- package/js/README.md +3 -2
- package/js/build/generate.mjs +12 -1
- package/js/build/source.mjs +6 -0
- package/js/cli-table.json +1 -1
- package/js/hosts/mcp.mjs +1 -1
- package/js/hosts/settings.mjs +13 -1
- package/js/inspect/checks-authoring.mjs +5 -3
- package/js/inspect/doctor.mjs +1 -1
- package/js/inspect/inventory.mjs +1 -1
- package/js/inspect/registry.mjs +20 -3
- package/js/inspect/status.mjs +7 -2
- package/js/lib/node-floor.mjs +30 -0
- package/js/maintain/setup.mjs +109 -21
- package/js/maintain/update.mjs +4 -15
- package/js/ui/cli.mjs +2 -2
- package/js/web/api.mjs +3 -0
- package/js/web/daemon.mjs +52 -0
- package/js/web/docs.mjs +149 -65
- package/js/web/server.mjs +5 -2
- package/package.json +6 -4
- package/registry.json +7 -0
- package/src/AGENT.md.tmpl +4 -0
- package/src/postures/mentor.md +1 -0
- package/src/skills/learn-mode.md +89 -0
- package/src/skills/pipeline.md +2 -2
- package/src/skills/quiz.md +2 -1
- package/src/skills/teach.md +2 -1
- package/themes/_TEMPLATE.json +1 -0
- package/themes/biker.json +1 -0
- package/themes/commentator.json +1 -0
- package/themes/cyberpunk.json +1 -0
- package/themes/gamer.json +1 -0
- package/themes/imperial.json +1 -0
- package/themes/joker.json +1 -0
- package/themes/marvin.json +1 -0
- package/themes/mean.json +1 -0
- package/themes/military.json +1 -0
- package/themes/neutral.json +1 -0
- package/themes/pirate.json +1 -0
- package/themes/sports.json +1 -0
- package/themes/verstappen.json +1 -0
- package/themes/wizard.json +1 -0
- package/web/dist/assets/Activity-C52LEb-2.js +1 -0
- package/web/dist/assets/ActivityDetail-CjNLRvXg.js +1 -0
- package/web/dist/assets/Dashboard-B6_sSCHi.js +2 -0
- package/web/dist/assets/Docs-Dj7BmCSA.js +5 -0
- package/web/dist/assets/{GreenhouseView-D0cbCJ5m.js → GreenhouseView-BALwjQsA.js} +1 -1
- package/web/dist/assets/Installs-BbffDQm3.js +2 -0
- package/web/dist/assets/JournalView-JpAQ8Hqe.js +1 -0
- package/web/dist/assets/Laws-DK7eCWSm.js +3 -0
- package/web/dist/assets/Library-CoamHelE.js +1 -0
- package/web/dist/assets/{Markdown-CkFyfgNf.js → Markdown-qjM2_ofS.js} +5 -5
- package/web/dist/assets/{OperatorHudView-CiAkAkQL.js → OperatorHudView-DVx1HlBx.js} +1 -1
- package/web/dist/assets/Personal-eaXb1zcj.js +1 -0
- package/web/dist/assets/{Tabs-B4HNrrCs.js → Tabs-BmGtt8E2.js} +1 -1
- package/web/dist/assets/api-Dc8f3ca-.js +1 -0
- package/web/dist/assets/drawable-Du_PZrQj.js +1 -0
- package/web/dist/assets/index-Bz_KMRDH.css +1 -0
- package/web/dist/assets/index-CNFH7o55.js +2 -0
- package/web/dist/index.html +3 -2
- package/QUICKSTART.md +0 -50
- package/SETUP.md +0 -1148
- package/docs/web/_groups.json +0 -45
- package/docs/web/about.md +0 -6
- package/docs/web/adapters-claude-code.md +0 -8
- package/docs/web/adapters-opencode-loads.md +0 -8
- package/docs/web/adapters-opencode-spec.md +0 -8
- package/docs/web/adapters-opencode.md +0 -8
- package/docs/web/agents.md +0 -8
- package/docs/web/authoring.md +0 -11
- package/docs/web/autostart.md +0 -9
- package/docs/web/claude-hooks.md +0 -37
- package/docs/web/cli.md +0 -6
- package/docs/web/collaboration.md +0 -27
- package/docs/web/configure-wiki.md +0 -9
- package/docs/web/design.md +0 -7
- package/docs/web/env-knobs.md +0 -9
- package/docs/web/exclusions.md +0 -26
- package/docs/web/first-session.md +0 -28
- package/docs/web/footprint.md +0 -38
- package/docs/web/foreman-mode.md +0 -33
- package/docs/web/glossary.md +0 -6
- package/docs/web/harnesses.md +0 -30
- package/docs/web/headless.md +0 -9
- package/docs/web/ingest-docs.md +0 -9
- package/docs/web/install-paths.md +0 -9
- package/docs/web/install-quick.md +0 -35
- package/docs/web/lsp-overview.md +0 -37
- package/docs/web/mcp-claude-code.md +0 -10
- package/docs/web/mcp-filesystem.md +0 -9
- package/docs/web/mcp-gitlab.md +0 -9
- package/docs/web/mcp-markitdown.md +0 -9
- package/docs/web/mcp-overview.md +0 -34
- package/docs/web/mcp-trouble.md +0 -9
- package/docs/web/mcp-verify.md +0 -9
- package/docs/web/migrate.md +0 -39
- package/docs/web/model.md +0 -20
- package/docs/web/pipelines.md +0 -24
- package/docs/web/plugin-activity.md +0 -23
- package/docs/web/plugin-context.md +0 -26
- package/docs/web/plugin-guard.md +0 -24
- package/docs/web/plugin-learn.md +0 -27
- package/docs/web/plugin-notify.md +0 -24
- package/docs/web/plugin-ponytail.md +0 -23
- package/docs/web/plugin-setup.md +0 -7
- package/docs/web/plugin-workflow.md +0 -25
- package/docs/web/plugins.md +0 -17
- package/docs/web/project-context.md +0 -9
- package/docs/web/rules.md +0 -30
- package/docs/web/run-anywhere.md +0 -9
- package/docs/web/self-improve.md +0 -8
- package/docs/web/setup-choices.md +0 -64
- package/docs/web/skills.md +0 -8
- package/docs/web/themes-author.md +0 -20
- package/docs/web/themes.md +0 -9
- package/docs/web/token-footprint.md +0 -7
- package/docs/web/trouble.md +0 -38
- package/docs/web/upgrade.md +0 -9
- package/docs/web/verify.md +0 -30
- package/docs/web/wiki.md +0 -7
- package/docs/web/worktree.md +0 -10
- package/docs/wiki.md +0 -43
- package/web/dist/assets/Activity-BjVp522q.js +0 -1
- package/web/dist/assets/ActivityDetail-8ep1bspW.js +0 -1
- package/web/dist/assets/Dashboard-OkC_PCH6.js +0 -2
- package/web/dist/assets/Docs-CKPWz00o.js +0 -2
- package/web/dist/assets/Installs-BtGGdoTt.js +0 -2
- package/web/dist/assets/JournalView-Ckl4dmzR.js +0 -1
- package/web/dist/assets/Laws-CLpdRRk8.js +0 -3
- package/web/dist/assets/Library-wPPuKQrI.js +0 -1
- package/web/dist/assets/Personal-D8CnDo9v.js +0 -1
- package/web/dist/assets/drawable-BiwaOo6x.js +0 -1
- package/web/dist/assets/index-C1yil5j1.js +0 -2
- package/web/dist/assets/index-CDPDk2vj.css +0 -1
package/js/build/generate.mjs
CHANGED
|
@@ -254,7 +254,18 @@ export function rebuildCommand(argv) {
|
|
|
254
254
|
* choose — which is the same failure mode `_footprint_of_dir`'s WARN exists to prevent, at a
|
|
255
255
|
* larger scale.
|
|
256
256
|
*/
|
|
257
|
-
export function cmdRebuildAll() {
|
|
257
|
+
export async function cmdRebuildAll() {
|
|
258
|
+
const rc = rebuildAll();
|
|
259
|
+
// A console started before an upgrade keeps serving the old `index.html`; restart it — only
|
|
260
|
+
// when stale, see `bounceDaemonIfRunning`. Lazy: no other verb in this module needs js/web/.
|
|
261
|
+
const { bounceDaemonIfRunning } = await import('../web/daemon.mjs');
|
|
262
|
+
await bounceDaemonIfRunning(null, (m) => printOut(`${m}
|
|
263
|
+
`), { onlyIfStale: true });
|
|
264
|
+
return rc;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** The rebuild itself, synchronous — `cmdRebuildAll` is it plus the console restart. */
|
|
268
|
+
export function rebuildAll() {
|
|
258
269
|
const targets = installTargets().filter(([h, s, r]) => installState(r, h, s) === 'active');
|
|
259
270
|
if (!targets.length) {
|
|
260
271
|
printOut('[rebuild-all] no active installs detected.\n');
|
package/js/build/source.mjs
CHANGED
|
@@ -168,6 +168,12 @@ export const CONFIG = path.join(ROOT, 'harness.config.json');
|
|
|
168
168
|
export const THEMES = path.join(ROOT, 'themes');
|
|
169
169
|
export const PLUGIN_SRC = path.join(ROOT, 'adapters', 'opencode', 'plugins');
|
|
170
170
|
export const WORKFLOW_SRC = path.join(ROOT, 'adapters', 'opencode', 'workflows');
|
|
171
|
+
/**
|
|
172
|
+
* The user docs: one folder per kind of reading, all under `docs/` so GitHub renders the same
|
|
173
|
+
* files the console serves. A page's id is its basename, so ids must be unique across folders.
|
|
174
|
+
*/
|
|
175
|
+
export const DOCS = path.join(ROOT, 'docs');
|
|
176
|
+
export const DOC_FOLDERS = ['understand', 'guides', 'concepts', 'reference'];
|
|
171
177
|
|
|
172
178
|
/**
|
|
173
179
|
* `_build_render.posture_names()` / `mode_names()` — discovered, never hardcoded, so a new
|
package/js/cli-table.json
CHANGED
package/js/hosts/mcp.mjs
CHANGED
|
@@ -68,7 +68,7 @@ export const MCP_PRESETS = {
|
|
|
68
68
|
+ 'install). Edit GITLAB_PERSONAL_ACCESS_TOKEN (scopes: api, read_repository) '
|
|
69
69
|
+ 'and GITLAB_API_URL before use. For a second instance (a self-hosted server), '
|
|
70
70
|
+ 'copy this block by hand under a new name and point it at the other URL — '
|
|
71
|
-
+ '
|
|
71
|
+
+ 'docs/guides/mcp.md has the pattern.',
|
|
72
72
|
block: {
|
|
73
73
|
type: 'local',
|
|
74
74
|
command: ['npx', '-y', '@zereight/mcp-gitlab'],
|
package/js/hosts/settings.mjs
CHANGED
|
@@ -483,6 +483,18 @@ export function hookShimPath(platform = process.platform) {
|
|
|
483
483
|
return path.join(shimHome(), ...shimRel(platform));
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
/**
|
|
487
|
+
* The paths the machine's hook shim names that are gone — `[]` when the shim is absent or live.
|
|
488
|
+
*
|
|
489
|
+
* Cheap on purpose: `status`, the doctor and EVERY CLI run call it (the run-time warning in
|
|
490
|
+
* `bin/geneseed-cli.mjs`), because a dead shim disables every hook of every Claude-shaped
|
|
491
|
+
* install and nothing else would say so. An absent shim is not dead — a checkout that has never
|
|
492
|
+
* emitted owns none — and an unreadable one is reported by the doctor, not here.
|
|
493
|
+
*/
|
|
494
|
+
export function shimDead() {
|
|
495
|
+
try { return shimDeadPaths(readText(hookShimPath())); } catch { return []; }
|
|
496
|
+
}
|
|
497
|
+
|
|
486
498
|
/**
|
|
487
499
|
* `_build_settings._hook_shim_body`, with the two volatile values INJECTED.
|
|
488
500
|
*
|
|
@@ -988,7 +1000,7 @@ export function migrateShape(commands, shimBody) {
|
|
|
988
1000
|
/**
|
|
989
1001
|
* `_build_settings._autostart_paths` — where a hand-written web-daemon autostart entry lives.
|
|
990
1002
|
*
|
|
991
|
-
* NOTHING IN THIS REPOSITORY HAS EVER WRITTEN ONE —
|
|
1003
|
+
* NOTHING IN THIS REPOSITORY HAS EVER WRITTEN ONE — the install guide tells the user to create them
|
|
992
1004
|
* by hand, and no .py/.mjs/.sh/.cmd in the tree contains `vbs`, `LaunchAgents` or `plist`. So
|
|
993
1005
|
* `migrate` REPORTS a stale one and never rewrites it, on the rule `settingsIntegrityCheck`
|
|
994
1006
|
* already states: an entry the manifest does not claim is "possibly user-authored; left alone".
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import {
|
|
16
|
-
CONFIG, PACK_ORDER, PLUGIN_SRC, RETIRED_RULE_IDS, ROOT, RULE_ID_RE, SRC, THEMES, canonFiles,
|
|
16
|
+
CONFIG, DOCS, DOC_FOLDERS, PACK_ORDER, PLUGIN_SRC, RETIRED_RULE_IDS, ROOT, RULE_ID_RE, SRC, THEMES, canonFiles,
|
|
17
17
|
knownRuleIds, ruleCanon, titleKey,
|
|
18
18
|
} from '../build/source.mjs';
|
|
19
19
|
import { themeFiles } from '../hosts/installs.mjs';
|
|
@@ -763,7 +763,7 @@ export function countTableProblems() {
|
|
|
763
763
|
|
|
764
764
|
// WHERE THE ONBOARDING COPY LIVES, AND WHY THIS READ MOVED. It used to open the one module
|
|
765
765
|
// that held the web console's onboarding prose. That prose has since moved into
|
|
766
|
-
// `docs
|
|
766
|
+
// `docs/<folder>/*.md`, and the counts in it render from `{N_LAWS}` / `{N_AGENTS}` / `{N_SKILLS}`
|
|
767
767
|
// — so the read was still succeeding against a file the sentences had left, all three arms
|
|
768
768
|
// below scored zero, and the check looked healthy while gating nothing. A templated count
|
|
769
769
|
// cannot drift; what these arms still catch is a maintainer typing the NUMBER into a page
|
|
@@ -773,7 +773,9 @@ export function countTableProblems() {
|
|
|
773
773
|
// byte moves. `web` stays fail-soft — a missing docs tree is not an authoring fault.
|
|
774
774
|
let web = '';
|
|
775
775
|
try {
|
|
776
|
-
web =
|
|
776
|
+
web = DOC_FOLDERS
|
|
777
|
+
.flatMap((f) => (isDir(path.join(DOCS, f))
|
|
778
|
+
? globSorted(path.join(DOCS, f), (n) => n.endsWith('.md')) : []))
|
|
777
779
|
.map(readText).join('\n');
|
|
778
780
|
} catch { web = ''; }
|
|
779
781
|
// ⚠ NOT FAIL-SOFT, UNLIKE `web` ABOVE, AND THE ASYMMETRY IS THE POINT. A missing `docs/web`
|
package/js/inspect/doctor.mjs
CHANGED
|
@@ -283,7 +283,7 @@ export function cmdDoctor(args) {
|
|
|
283
283
|
// (`js/build/generate.mjs`), which forwards `--theme` and nothing else. So the front door
|
|
284
284
|
// answered `unrecognized arguments: --sync-themes` to anyone who followed doctor's own
|
|
285
285
|
// advice, for as long as the premise went unchecked. The flag belongs to the GENERATOR,
|
|
286
|
-
// whose binary is `geneseed-build` — which is what `README.md` and
|
|
286
|
+
// whose binary is `geneseed-build` — which is what `README.md` and the install guide have said
|
|
287
287
|
// all along. A hint is a command; if it is not runnable it is decoration.
|
|
288
288
|
printOut(' tip: a theme is missing a key another theme defines — run '
|
|
289
289
|
+ '`geneseed-build --sync-themes` to fill it from _TEMPLATE.json, '
|
package/js/inspect/inventory.mjs
CHANGED
|
@@ -108,7 +108,7 @@ export const SKILL_CLASS = {
|
|
|
108
108
|
// `understand` before, which made that class a grab-bag.
|
|
109
109
|
wiki: 'harness', geneseed: 'harness', rule: 'harness', profile: 'harness',
|
|
110
110
|
herdr: 'harness', 'skill-forge': 'harness', 'consolidate-memory': 'harness',
|
|
111
|
-
teach: 'learn', quiz: 'learn',
|
|
111
|
+
teach: 'learn', quiz: 'learn', 'learn-mode': 'learn',
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
/** `k in dict` — own keys only, where `k in obj` also finds `Object.prototype` members. */
|
package/js/inspect/registry.mjs
CHANGED
|
@@ -17,10 +17,10 @@ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync } from 'n
|
|
|
17
17
|
import path from 'node:path';
|
|
18
18
|
import os from 'node:os';
|
|
19
19
|
|
|
20
|
-
import { expanduser } from '../hosts/hosts.mjs';
|
|
20
|
+
import { expanduser, resolvePath } from '../hosts/hosts.mjs';
|
|
21
21
|
import { writeText, isFile, isDir } from '../lib/fs.mjs';
|
|
22
22
|
import { parseJson, jsonDumpsIndent } from '../lib/json.mjs';
|
|
23
|
-
import { toPlatformPath } from '../lib/paths.mjs';
|
|
23
|
+
import { toPlatformPath, within } from '../lib/paths.mjs';
|
|
24
24
|
|
|
25
25
|
/** `_install_registry._path()` — `$XDG_CONFIG_HOME/geneseed/installs.json`. */
|
|
26
26
|
function registryPath() {
|
|
@@ -28,6 +28,21 @@ function registryPath() {
|
|
|
28
28
|
return path.join(base, 'geneseed', 'installs.json');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Is `root` a scratch install — inside the OS temp dir while the registry is not?
|
|
33
|
+
*
|
|
34
|
+
* Agent sessions emit into scratchpads under `%TEMP%`; recorded, each one is rebuilt by every
|
|
35
|
+
* `rebuild-all` and listed by every `status` for as long as the folder survives. The second
|
|
36
|
+
* clause is what keeps every test and golden cell meaningful: their registry lives in a
|
|
37
|
+
* sandbox under the temp dir too, so their temp roots are not scratch RELATIVE TO IT.
|
|
38
|
+
* `within` is case-insensitive on win32, so `C:\Users\X\AppData\Local\Temp` and its
|
|
39
|
+
* lower-cased spelling are one directory.
|
|
40
|
+
*/
|
|
41
|
+
export function isScratchRoot(root, registryFile) {
|
|
42
|
+
const tmp = resolvePath(os.tmpdir());
|
|
43
|
+
return within(resolvePath(root), tmp) && !within(resolvePath(registryFile), tmp);
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
function registryLoad() {
|
|
32
47
|
try {
|
|
33
48
|
const data = parseJson(readFileSync(registryPath(), 'utf8'));
|
|
@@ -59,6 +74,7 @@ export function registryRecord(dir) {
|
|
|
59
74
|
try {
|
|
60
75
|
let root;
|
|
61
76
|
try { root = realpathSync.native(dir); } catch { root = path.resolve(dir); }
|
|
77
|
+
if (isScratchRoot(root, registryPath())) return;
|
|
62
78
|
const cur = registryLoad();
|
|
63
79
|
if (!cur.includes(root)) registrySave([...cur, root]);
|
|
64
80
|
} catch { /* a registry hiccup must never fail a build */ }
|
|
@@ -75,6 +91,7 @@ export function registryRecord(dir) {
|
|
|
75
91
|
*/
|
|
76
92
|
export function registryRoots() {
|
|
77
93
|
const original = registryLoad();
|
|
94
|
+
const file = registryPath();
|
|
78
95
|
const out = [];
|
|
79
96
|
const kept = [];
|
|
80
97
|
const seen = new Set();
|
|
@@ -96,7 +113,7 @@ export function registryRoots() {
|
|
|
96
113
|
let key;
|
|
97
114
|
try { key = existsSync(root) ? realpathSync.native(root) : root; } catch { key = root; }
|
|
98
115
|
if (seen.has(key)) continue;
|
|
99
|
-
if (isDir(root) && isFile(path.join(root, '.geneseed-emit'))) {
|
|
116
|
+
if (isDir(root) && isFile(path.join(root, '.geneseed-emit')) && !isScratchRoot(root, file)) {
|
|
100
117
|
seen.add(key);
|
|
101
118
|
kept.push(root);
|
|
102
119
|
out.push(root);
|
package/js/inspect/status.mjs
CHANGED
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
defaultTheme, installedDefaults, installTargets, manifestIsClaude, readJsonMaybe, readMaybe,
|
|
74
74
|
} from '../hosts/installs.mjs';
|
|
75
75
|
import { installProfile, rebuildCommand } from '../build/generate.mjs';
|
|
76
|
+
import { shimDead } from '../hosts/settings.mjs';
|
|
76
77
|
import { printOut } from '../lib/fs.mjs';
|
|
77
78
|
import { codePointLength, padEndToWidth } from '../lib/text.mjs';
|
|
78
79
|
|
|
@@ -208,7 +209,9 @@ export function gateSummary(cfgDirs) {
|
|
|
208
209
|
total += 1;
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
|
-
|
|
212
|
+
// `dead` is the machine shim's missing targets: every gate below is armed on paper and never
|
|
213
|
+
// runs, so it outranks `armed` in the row.
|
|
214
|
+
return { standing_down: standingDown, asks, total, dead: shimDead() };
|
|
212
215
|
}
|
|
213
216
|
|
|
214
217
|
/** `_harness_status._status_data`. */
|
|
@@ -350,7 +353,9 @@ export function statusLines(d, color = false) {
|
|
|
350
353
|
// panels carry no `gates` key and must render unchanged.
|
|
351
354
|
if (d.gates) {
|
|
352
355
|
const g = d.gates;
|
|
353
|
-
const armed = g.
|
|
356
|
+
const armed = g.dead?.length
|
|
357
|
+
? `DEAD — hook shim points at ${g.dead.join(', ')}; run: geneseed rebuild-all`
|
|
358
|
+
: g.standing_down.length
|
|
354
359
|
? `STANDING DOWN for this cwd (excludes.json in ${g.standing_down.join(', ')})`
|
|
355
360
|
: 'armed';
|
|
356
361
|
const byRule = Object.entries(g.asks).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Node floor — the FIRST import of all three binaries, so an old Node gets one sentence
|
|
3
|
+
* instead of a stack trace from whichever API it lacks.
|
|
4
|
+
*
|
|
5
|
+
* `engines` in package.json only makes npm warn, and `npx geneseed`, a global install and the
|
|
6
|
+
* hook shim all run the binaries directly. ESM links the whole graph before any body runs, so
|
|
7
|
+
* this only helps while every file still PARSES on the old Node; it runs before any of them
|
|
8
|
+
* executes. The hook exits 0: a hook must never block a tool call, and the message on stderr
|
|
9
|
+
* is what the host surfaces. Nothing here may print to stdout — the hook's decision channel.
|
|
10
|
+
*/
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
|
|
13
|
+
export const NODE_FLOOR = '22.3.0';
|
|
14
|
+
|
|
15
|
+
/** Numeric, not string, comparison: '22.10.0' is above '22.3.0'. */
|
|
16
|
+
export function belowFloor(version, floor = NODE_FLOOR) {
|
|
17
|
+
const v = version.replace(/^v/, '').split('.').map(Number);
|
|
18
|
+
const f = floor.split('.').map(Number);
|
|
19
|
+
for (let i = 0; i < 3; i += 1) {
|
|
20
|
+
if ((v[i] || 0) !== f[i]) return (v[i] || 0) < f[i];
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (belowFloor(process.versions.node)) {
|
|
26
|
+
process.stderr.write(`geneseed needs Node 22.3 or newer; this is Node ${process.versions.node}. `
|
|
27
|
+
+ 'Install a current Node from https://nodejs.org (or your package manager), then run the '
|
|
28
|
+
+ 'command again.\n');
|
|
29
|
+
process.exit(path.basename(process.argv[1] || '').startsWith('geneseed-hook') ? 0 : 1);
|
|
30
|
+
}
|
package/js/maintain/setup.mjs
CHANGED
|
@@ -50,9 +50,10 @@ import { discoverNames, PACK_ORDER } from '../build/source.mjs';
|
|
|
50
50
|
import { exportImprovements } from '../inspect/diff.mjs';
|
|
51
51
|
import { cmdDoctor } from '../inspect/doctor.mjs';
|
|
52
52
|
import { setupBuildArgs } from '../build/generate.mjs';
|
|
53
|
-
import { GLOBAL_MANIFEST, opencodeConfigDir } from '../hosts/hosts.mjs';
|
|
53
|
+
import { GLOBAL_MANIFEST, HOSTS, opencodeConfigDir } from '../hosts/hosts.mjs';
|
|
54
54
|
import {
|
|
55
|
-
defaultMode, defaultPosture, defaultTheme,
|
|
55
|
+
claudeCfg, defaultMode, defaultPosture, defaultTheme, EMIT_HOST_SCOPE, installedDefaults,
|
|
56
|
+
readJsonMaybe, themeFiles,
|
|
56
57
|
} from '../hosts/installs.mjs';
|
|
57
58
|
import { printOut, printErr } from '../lib/fs.mjs';
|
|
58
59
|
import { which } from '../lib/paths.mjs';
|
|
@@ -457,39 +458,77 @@ export function lspPrereqs() {
|
|
|
457
458
|
+ "SDKMAN `sdk install java 21-tem`, or your distro's package"]];
|
|
458
459
|
}
|
|
459
460
|
|
|
461
|
+
/** The name each host goes by on screen — the same four labels `hostTools` prints. */
|
|
462
|
+
const TOOL_NAME = {
|
|
463
|
+
opencode: 'OpenCode', claude: 'Claude Code', bob: 'IBM Bob', openclaude: 'OpenClaude',
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Where an emit put its root file, and — for the Claude-shaped hosts — its hooks.
|
|
468
|
+
*
|
|
469
|
+
* READ BACK, NOT RE-SPELLED: the placements differ by host and scope (Bob global's preamble is
|
|
470
|
+
* `rules/geneseed.md`, a Claude project's CLAUDE.md sits beside `.claude/`, a project's hooks go
|
|
471
|
+
* to `settings.local.json`, Bob global's to `settings/settings.json`), and the emit already
|
|
472
|
+
* records each one in the install's manifest (`managed.claude_md`, `settings_file`,
|
|
473
|
+
* `settings_hooks`). A summary that recomputed them would be a second owner of every rule.
|
|
474
|
+
*/
|
|
475
|
+
function emitFacts(emit, out) {
|
|
476
|
+
const [host, scope] = EMIT_HOST_SCOPE.get(emit) ?? ['files', 'project'];
|
|
477
|
+
if (host === 'files' || host === 'opencode') {
|
|
478
|
+
const dir = emit === 'opencode-global' ? opencodeConfigDir() : resolveOut(out || 'Harness');
|
|
479
|
+
return { host, rootFile: path.join(dir, 'AGENT.md') };
|
|
480
|
+
}
|
|
481
|
+
const spec = HOSTS.find((h) => h.host === host);
|
|
482
|
+
const cfg = claudeCfg(scope === 'global' ? spec.configDir() : resolveOut(out || '.'), scope, host);
|
|
483
|
+
const managed = readJsonMaybe(path.join(cfg, GLOBAL_MANIFEST))?.managed ?? {};
|
|
484
|
+
const rootFile = managed.claude_md?.rel
|
|
485
|
+
? path.resolve(cfg, managed.claude_md.rel)
|
|
486
|
+
: path.join(cfg, host === 'bob' ? path.join('rules', 'geneseed.md') : spec.agentFile);
|
|
487
|
+
return {
|
|
488
|
+
host,
|
|
489
|
+
rootFile,
|
|
490
|
+
settings: managed.settings_file ? path.join(cfg, managed.settings_file) : null,
|
|
491
|
+
hookGroups: Array.isArray(managed.settings_hooks) ? managed.settings_hooks.length : 0,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
460
495
|
/**
|
|
461
496
|
* `_harness_setup._setup_summary_lines` — the post-build report as `[kind, text]` rows,
|
|
462
497
|
* `kind` being ok | warn | info.
|
|
463
498
|
*
|
|
464
499
|
* `ok` is the build's own success, so the first row is the only one that can say "build
|
|
465
|
-
* failed"; every later row assumes a tree on disk.
|
|
466
|
-
*
|
|
467
|
-
*
|
|
500
|
+
* failed"; every later row assumes a tree on disk. HOST-AWARE since the install-hardening
|
|
501
|
+
* pass: it used to know only OpenCode's AGENT.md, so a successful claude-global install ended
|
|
502
|
+
* on "expected AGENT.md … but it is not there" and "start a NEW OpenCode session". The
|
|
503
|
+
* global-install warning is the row with real behaviour in it: a bundle or project emit made
|
|
504
|
+
* while an OpenCode GLOBAL install exists is loaded by nothing, and saying so is the
|
|
505
|
+
* difference between a wizard and a form.
|
|
468
506
|
*/
|
|
469
507
|
export function setupSummaryLines(theme, emit, out, root, ok) {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
: path.join(resolveOut(out || 'Harness'), 'AGENT.md');
|
|
508
|
+
const f = emitFacts(emit, out ?? root);
|
|
509
|
+
const name = path.basename(f.rootFile);
|
|
473
510
|
const lines = [];
|
|
474
|
-
if (ok && existsSync(
|
|
475
|
-
lines.push(['ok',
|
|
511
|
+
if (ok && existsSync(f.rootFile)) {
|
|
512
|
+
lines.push(['ok', `${name} written to ${f.rootFile}`]);
|
|
476
513
|
} else if (ok) {
|
|
477
|
-
lines.push(['warn', `expected
|
|
514
|
+
lines.push(['warn', `expected ${name} at ${f.rootFile} but it is not there`]);
|
|
478
515
|
} else {
|
|
479
516
|
lines.push(['warn', 'build failed — see the output above']);
|
|
480
517
|
}
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
|
|
518
|
+
if (ok && f.settings) {
|
|
519
|
+
lines.push(['ok', `${f.hookGroups} hook groups wired in ${f.settings}`]);
|
|
520
|
+
if (!process.env.GENESEED_LLM) {
|
|
521
|
+
lines.push(['info', 'memory learning is off until you set GENESEED_LLM '
|
|
522
|
+
+ '(see docs/reference/environment.md)']);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (emit === 'files') {
|
|
526
|
+
lines.push(['info', `point your tool's instructions at ${f.rootFile}`]);
|
|
489
527
|
}
|
|
490
528
|
try {
|
|
491
529
|
const cfg = opencodeConfigDir();
|
|
492
|
-
|
|
530
|
+
// Only for an emit OpenCode itself would load — a Claude or Bob install is not shadowed.
|
|
531
|
+
if (['opencode', 'files'].includes(emit) && existsSync(path.join(cfg, GLOBAL_MANIFEST))) {
|
|
493
532
|
lines.push(['warn', `a global install exists at ${cfg} — OpenCode loads THAT, `
|
|
494
533
|
+ `not this build; re-run with 'opencode-global' to change it`]);
|
|
495
534
|
}
|
|
@@ -500,7 +539,12 @@ export function setupSummaryLines(theme, emit, out, root, ok) {
|
|
|
500
539
|
lines.push(present ? ['ok', `${label} present`] : ['warn', `${label} missing — ${hint}`]);
|
|
501
540
|
}
|
|
502
541
|
}
|
|
503
|
-
|
|
542
|
+
const tool = TOOL_NAME[f.host];
|
|
543
|
+
lines.push(['info', tool
|
|
544
|
+
? `theme is now '${theme}' — start a NEW ${tool} session to load it`
|
|
545
|
+
: `theme is now '${theme}' — start a NEW session in your tool to load it`]);
|
|
546
|
+
lines.push(['info', 'what was installed and what each piece does: '
|
|
547
|
+
+ 'https://github.com/Arylmera/Geneseed/blob/main/docs/understand/on-your-machine.md']);
|
|
504
548
|
return lines;
|
|
505
549
|
}
|
|
506
550
|
|
|
@@ -593,5 +637,49 @@ export function cmdSetup() {
|
|
|
593
637
|
+ ' geneseed build --emit opencode-global --theme neutral\n');
|
|
594
638
|
return 1;
|
|
595
639
|
}
|
|
640
|
+
if (!toolsReport()) return 1;
|
|
596
641
|
return setupLines();
|
|
597
642
|
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* The AI coding tools Geneseed installs into, as `[label, present, url]` rows in HOSTS order.
|
|
646
|
+
*
|
|
647
|
+
* PRESENT MEANS THE COMMAND IS ON PATH, and nothing else. The config dir looked like a second
|
|
648
|
+
* signal — a desktop-only install has one and no command — but GENESEED ITSELF CREATES IT: a
|
|
649
|
+
* `bob-global` emit writes `~/.bob` on a machine that never had Bob, and the first run of this
|
|
650
|
+
* check reported Bob present on exactly such a machine. A wrong "ok" sends the user to an
|
|
651
|
+
* install mode for a tool they cannot open; a wrong "not found" costs one line they can ignore.
|
|
652
|
+
*
|
|
653
|
+
* NO SPAWN, deliberately. `which` walks PATH by hand, so asking costs nothing on the CLI's
|
|
654
|
+
* child_process allow-list — a `claude --version` probe would have been a third entry there to
|
|
655
|
+
* learn nothing `which` does not already say. `lookup` is the seam for the test, which cannot
|
|
656
|
+
* rely on what this machine happens to have installed.
|
|
657
|
+
*/
|
|
658
|
+
export function hostTools({ lookup = which } = {}) {
|
|
659
|
+
return [
|
|
660
|
+
['OpenCode', 'opencode', 'https://opencode.ai/docs/'],
|
|
661
|
+
['Claude Code', 'claude', 'https://code.claude.com/docs/en/setup'],
|
|
662
|
+
['IBM Bob', 'bob', 'https://bob.ibm.com/'],
|
|
663
|
+
['OpenClaude', 'openclaude', 'https://openclaude.gitlawb.com/'],
|
|
664
|
+
].map(([label, cmd, url]) => [label, Boolean(lookup(cmd)), url]);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Show which tools are here before asking anything — the install-mode question is easier to
|
|
669
|
+
* answer knowing it — and when NONE is, ask whether to stop and install one first. It never
|
|
670
|
+
* installs one: on a managed machine that is IT's call, and each tool has its own installer.
|
|
671
|
+
* A missing tool among several present is a line, not a question; nobody needs all four.
|
|
672
|
+
* `false` is the user choosing to stop.
|
|
673
|
+
*/
|
|
674
|
+
export function toolsReport(tools = hostTools()) {
|
|
675
|
+
printOut('AI coding tools on this machine:\n');
|
|
676
|
+
for (const [label, present, url] of tools) {
|
|
677
|
+
printOut(present ? ` ok ${label}\n` : ` -- ${label} not on PATH — install: ${url}\n`);
|
|
678
|
+
}
|
|
679
|
+
if (tools.some(([, present]) => present)) { printOut('\n'); return true; }
|
|
680
|
+
printOut('\nNone of them is installed. Install the one you use (links above), then run setup '
|
|
681
|
+
+ 'again — or go on and pick the plain bundle, which any AGENT.md tool can read.\n');
|
|
682
|
+
if (confirm('Continue without one?', false)) { printOut('\n'); return true; }
|
|
683
|
+
printOut('Stopped. Run setup again once a tool is installed.\n');
|
|
684
|
+
return false;
|
|
685
|
+
}
|
package/js/maintain/update.mjs
CHANGED
|
@@ -50,7 +50,7 @@ import { appendText, copyFile, printOut, printErr, readText, writeText } from '.
|
|
|
50
50
|
import { toPlatformPath, which } from '../lib/paths.mjs';
|
|
51
51
|
import { WHITESPACE, parseIntStrict, stripWhitespace } from '../lib/text.mjs';
|
|
52
52
|
import { NO_WINDOW } from '../lib/proc.mjs';
|
|
53
|
-
import {
|
|
53
|
+
import { bounceDaemonIfRunning } from '../web/daemon.mjs';
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* `_CREDS_RE` / `_redact_url_creds` — strip a `user[:token]@` userinfo out of any URL in
|
|
@@ -578,20 +578,9 @@ export async function rebuildBundle(here, out, theme, emit, rootDir, log) {
|
|
|
578
578
|
{ cwd: String(here), stdio: 'inherit' });
|
|
579
579
|
if (proc.status !== 0) return proc.status === null ? 1 : proc.status;
|
|
580
580
|
// If a web daemon is running, bounce it so the new source and the freshly rebuilt web/dist
|
|
581
|
-
// take effect — otherwise the open PWA keeps hitting the old code.
|
|
582
|
-
// upgrade
|
|
583
|
-
|
|
584
|
-
// once the job is recorded as finished. `js/web/jobs.mjs` sets the variable; this is the
|
|
585
|
-
// side that reads it, and `upgrade/the-web-job-marker-...` is what gates the pair.
|
|
586
|
-
if (process.env.GENESEED_WEB_JOB) {
|
|
587
|
-
log('[geneseed] web daemon will restart itself after this job to load the new code.');
|
|
588
|
-
return 0;
|
|
589
|
-
}
|
|
590
|
-
try {
|
|
591
|
-
await restartDaemon(theme, 4747, false, true);
|
|
592
|
-
} catch (e) { // never fail an upgrade on this
|
|
593
|
-
log(`[geneseed] ⚠️ could not refresh the web daemon (${e && e.message ? e.message : e}) — \`geneseed web restart\` manually if it was running.`);
|
|
594
|
-
}
|
|
581
|
+
// take effect — otherwise the open PWA keeps hitting the old code. The web-job exception
|
|
582
|
+
// (`upgrade/the-web-job-marker-...` gates it) lives with the helper.
|
|
583
|
+
await bounceDaemonIfRunning(theme, log);
|
|
595
584
|
return 0;
|
|
596
585
|
}
|
|
597
586
|
|
package/js/ui/cli.mjs
CHANGED
|
@@ -69,7 +69,7 @@ function load() {
|
|
|
69
69
|
try {
|
|
70
70
|
return JSON.parse(readFileSync(CLI_JSON, 'utf-8'));
|
|
71
71
|
} catch {
|
|
72
|
-
return { prog: '
|
|
72
|
+
return { prog: 'geneseed', commands: [] };
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -88,7 +88,7 @@ const pageArgs = (args) => (args ?? [])
|
|
|
88
88
|
export function cliReference() {
|
|
89
89
|
const data = load();
|
|
90
90
|
return {
|
|
91
|
-
prog: data.prog ?? '
|
|
91
|
+
prog: data.prog ?? 'geneseed',
|
|
92
92
|
commands: (data.commands ?? []).map((c) => ({
|
|
93
93
|
name: c.name ?? '',
|
|
94
94
|
help: c.help ?? '',
|
package/js/web/api.mjs
CHANGED
|
@@ -819,6 +819,9 @@ export function apiOverview(state) {
|
|
|
819
819
|
install,
|
|
820
820
|
target: state.target,
|
|
821
821
|
deployed: deployed(state),
|
|
822
|
+
// True when the files on disk changed under this server since it started (an upgrade).
|
|
823
|
+
// `serve` installs the comparison; a state built without a server has none and is false.
|
|
824
|
+
daemon_stale: Boolean(state.daemonStale?.()),
|
|
822
825
|
counts: {
|
|
823
826
|
agents: inv.agents.length,
|
|
824
827
|
skills: inv.skills.length,
|
package/js/web/daemon.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { printOut, writeText } from '../lib/fs.mjs';
|
|
|
13
13
|
import { isTruthy, jsonDumpsCompact } from '../lib/json.mjs';
|
|
14
14
|
import { webState } from './api.mjs';
|
|
15
15
|
import { spawn } from 'node:child_process';
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
16
17
|
import { chmodSync, mkdirSync, openSync, readFileSync, unlinkSync } from 'node:fs';
|
|
17
18
|
import { join, resolve as pathResolve } from 'node:path';
|
|
18
19
|
|
|
@@ -20,6 +21,29 @@ const ROOT = pathResolve(import.meta.dirname, '..', '..');
|
|
|
20
21
|
|
|
21
22
|
// ---- the daemon record -----------------------------------------------------
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Which code a console serves: the package version and a hash of the `web/dist/index.html` it
|
|
26
|
+
* would load. A daemon writes the stamp it started with into its record; an npm upgrade or a
|
|
27
|
+
* `git pull` that replaces `web/dist/` changes the stamp on disk, and the daemon — still
|
|
28
|
+
* serving the old `index.html`, whose chunk hashes may no longer exist — is then STALE.
|
|
29
|
+
* Read fresh on every call: the comparison is against the files as they are NOW.
|
|
30
|
+
*/
|
|
31
|
+
export function codeStamp(root = ROOT) {
|
|
32
|
+
const read = (rel) => { try { return readFileSync(join(root, rel)); } catch { return ''; } };
|
|
33
|
+
let version = 'unknown';
|
|
34
|
+
try { version = JSON.parse(read('package.json')).version ?? version; } catch { /* unknown */ }
|
|
35
|
+
const html = createHash('sha256').update(read(join('web', 'dist', 'index.html'))).digest('hex');
|
|
36
|
+
return `${version}:${html.slice(0, 12)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A record written by an older daemon carries no `code` — and is stale by definition. */
|
|
40
|
+
export function daemonStale(record, root = ROOT) {
|
|
41
|
+
return record?.code !== codeStamp(root);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const STALE_LINE = '[web] running OLD code (started before the last upgrade) — '
|
|
45
|
+
+ 'geneseed web restart';
|
|
46
|
+
|
|
23
47
|
const statePath = (target) => join(target, '.geneseed-web.json');
|
|
24
48
|
|
|
25
49
|
export function readDaemon(target) {
|
|
@@ -268,6 +292,7 @@ export async function statusDaemon(theme = null) {
|
|
|
268
292
|
const st = await liveDaemon(target);
|
|
269
293
|
if (st) {
|
|
270
294
|
printOut(`[web] running on ${st.url} (theme: ${none(st.theme)}, pid ${none(st.pid)})\n`);
|
|
295
|
+
if (daemonStale(st)) printOut(`${STALE_LINE}\n`);
|
|
271
296
|
return 0;
|
|
272
297
|
}
|
|
273
298
|
// EXIT 1, which is the verb's whole contract for a script. The snapshot's `<exit>`
|
|
@@ -276,6 +301,33 @@ export async function statusDaemon(theme = null) {
|
|
|
276
301
|
return 1;
|
|
277
302
|
}
|
|
278
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Bounce a running console so it serves the code now on disk — `upgrade` after it rebuilt,
|
|
306
|
+
* `rebuild-all` when the running one is stale. EXCEPT inside a web job (`GENESEED_WEB_JOB`,
|
|
307
|
+
* set by `js/web/jobs.mjs`): restarting here would kill the process tracking that very job,
|
|
308
|
+
* leaving the console stuck on 'running'; the server restarts itself after an update job, and
|
|
309
|
+
* shows its stale banner otherwise. `onlyIfStale` is `rebuild-all`'s: after an `upgrade`
|
|
310
|
+
* (which bounces, then runs `rebuild-all`) the console is current, and bouncing it again
|
|
311
|
+
* would drop the open tab for nothing. Never throws — no rebuild fails over a console.
|
|
312
|
+
*/
|
|
313
|
+
export async function bounceDaemonIfRunning(theme, log, { onlyIfStale = false } = {}) {
|
|
314
|
+
if (onlyIfStale) {
|
|
315
|
+
const st = readDaemon(webState(theme).target);
|
|
316
|
+
if (!st || !daemonStale(st)) return;
|
|
317
|
+
}
|
|
318
|
+
if (process.env.GENESEED_WEB_JOB) {
|
|
319
|
+
log(onlyIfStale
|
|
320
|
+
? '[geneseed] the web console is running old code — use its Restart banner.'
|
|
321
|
+
: '[geneseed] web daemon will restart itself after this job to load the new code.');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
await restartDaemon(theme, 4747, false, true);
|
|
326
|
+
} catch (e) {
|
|
327
|
+
log(`[geneseed] ⚠️ could not refresh the web daemon (${e && e.message ? e.message : e}) — \`geneseed web restart\` manually if it was running.`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
279
331
|
export async function restartDaemon(theme = null, port = 4747, openBrowser = true,
|
|
280
332
|
onlyIfRunning = false) {
|
|
281
333
|
const { target } = webState(theme);
|