@zenera/cli 1.1.11 → 1.1.14

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.
Files changed (36) hide show
  1. package/README.md +20 -0
  2. package/dist/banner.js +69 -47
  3. package/dist/commands/index.js +3 -0
  4. package/dist/commands/init.js +8 -4
  5. package/dist/commands/inspect.js +23 -5
  6. package/dist/commands/memory.d.ts +3 -0
  7. package/dist/commands/memory.js +375 -0
  8. package/dist/engine.js +15 -9
  9. package/dist/main.js +9 -0
  10. package/dist/podman.js +43 -9
  11. package/dist/projects.js +10 -2
  12. package/dist/scaffold.js +22 -1
  13. package/dist/tui/app.js +19 -11
  14. package/dist/tui/theme.d.ts +7 -0
  15. package/dist/tui/theme.js +18 -11
  16. package/package.json +2 -2
  17. package/templates/editor/.github/copilot-instructions.md +151 -43
  18. package/templates/editor/.github/prompts/review-project.prompt.md +5 -0
  19. package/templates/editor/.github/prompts/sync-with-spec.prompt.md +7 -4
  20. package/templates/editor/.github/skills/zen-cli/SKILL.md +3 -0
  21. package/templates/editor/.github/skills/zen-cli/references/memory.md +113 -0
  22. package/templates/editor/.github/skills/zen-cli/references/rag.md +2 -2
  23. package/templates/editor/.github/skills/zen-cli/references/run.md +3 -1
  24. package/templates/editor/.github/skills/zen-memory/SKILL.md +555 -0
  25. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +16 -16
  26. package/templates/editor/.github/skills/zen-rag-schema/SKILL.md +2 -2
  27. package/templates/editor/.vscode/settings.json +4 -2
  28. package/templates/parts/exa.prompt.md.tmpl +4 -0
  29. package/templates/parts/exa.spec.md.tmpl +2 -0
  30. package/templates/project/INSTRUCTIONS.md +8 -0
  31. package/templates/project/SPECIFICATION.md.tmpl +47 -0
  32. package/templates/project/agents/prompts/{default.md → default.md.tmpl} +4 -0
  33. package/templates/project/agents.yaml.tmpl +4 -0
  34. package/templates/project/gitignore +7 -1
  35. package/templates/project/scripts/_setup.sh +101 -0
  36. package/templates/project/SPECIFICATION.md +0 -6
package/README.md CHANGED
@@ -134,17 +134,27 @@ Credentials live in `$HOME`, so the project never contains a secret.
134
134
 
135
135
  ```
136
136
  my-project/
137
+ SPECIFICATION.md what this is for — the intent
137
138
  INSTRUCTIONS.md house rules, prepended to every agent
138
139
  agents.yaml who exists, what they may reach for
139
140
  agents/
140
141
  prompts/<name>.md each agent's own brief
141
142
  skills/<name>/ knowledge loaded on demand, not always-on
143
+ assets/ reference material, read-only at /assets
144
+ sandbox/Dockerfile the container commands run in
145
+ scripts/_setup.sh the one command that initialises the project
142
146
  sessions/ one workspace, memory and trajectory each
143
147
  <id>/
144
148
  workspace/ what the agents can read and write
145
149
  runs/<id>/ input, output, state, report.html, meta
146
150
  ```
147
151
 
152
+ `SPECIFICATION.md` comes first because the rest implements it. The one `zen init`
153
+ writes is a true specification of the project it just scaffolded; replace it with
154
+ what you are building, then run `/sync-with-spec` in your editor to make the
155
+ files match. See
156
+ [docs/specification.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/specification.md).
157
+
148
158
  `zen init` also writes `.github/copilot-instructions.md` — a standing brief that
149
159
  explains this runtime to whatever coding agent you have open in that folder, so
150
160
  you are not expected to hand-author `agents.yaml`.
@@ -160,6 +170,7 @@ you are not expected to hand-author `agents.yaml`.
160
170
  | `models` | What this machine can use — list, search, test, pick. |
161
171
  | `run` | Runs the project — the TUI on a terminal, a single answer otherwise. |
162
172
  | `inspect` | Opens or rebuilds a run's `report.html`. |
173
+ | `memory` | What the agents remember — size, listing, one node, or a whole page. |
163
174
  | `check` | Validates the project and every file it names, and asks the models. |
164
175
  | `sandbox` | Checks and prepares the container that command-line tools run in. |
165
176
  | `cache` | What work has been kept, and getting rid of it. |
@@ -459,11 +470,20 @@ terminal:
459
470
 
460
471
  ## Documentation
461
472
 
473
+ [docs/specification.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/specification.md)
474
+ — writing the specification the project implements ·
462
475
  [docs/projects.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/projects.md)
463
476
  — the folder a project is ·
464
477
  [docs/agents-yaml.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/agents-yaml.md)
465
478
  — every key in the configuration file.
466
479
 
480
+ [docs/knowledge.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/knowledge.md)
481
+ — a searchable document index ·
482
+ [docs/integrations.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/integrations.md)
483
+ — an API description, searchable and mockable ·
484
+ [docs/memory.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/memory.md)
485
+ — what agents keep between sessions.
486
+
467
487
  ## License
468
488
 
469
489
  Early days and moving fast — issues, questions and pull requests are welcome.
package/dist/banner.js CHANGED
@@ -8,64 +8,80 @@ import { dim, note } from "./term.js";
8
8
  // terminal — a pipeline asking for the answer gets the answer, and a CI log is
9
9
  // not decorated with block letters.
10
10
  //
11
- // The font is here rather than pulled in as a dependency because it is a
12
- // kilobyte of constant and the whole CLI is otherwise Node's own. Two things
13
- // about its shape are deliberate: the stroke is **two cells wide**, because a
14
- // one-cell stem in a dotted fill reads as noise rather than as a letter, and
15
- // the ink is the dotted block (U+2592) rather than a solid one, which makes a
16
- // thick stroke a screen-print instead of a slab.
11
+ // The font is here rather than pulled in as a dependency because it is a couple
12
+ // of kilobytes of constant and the whole CLI is otherwise Node's own. It is the
13
+ // shadowed block face: a solid stroke lit from the top left with a box-drawing
14
+ // bevel down its right side and along its foot, so the letters read as raised
15
+ // rather than as a texture. Glyphs are variable width and carry their own
16
+ // spacing, so they abut directly — the gap belongs to the letter, not the join.
17
17
  //
18
18
  // A terminal too narrow for the art gets the wordmark on one line. A banner
19
19
  // that wraps is worse than no banner.
20
20
  // ---------------------------------------------------------------------------
21
- const HEIGHT = 5;
22
- const WIDTH = 6;
21
+ const HEIGHT = 6;
22
+ /** Between the two words. Wide enough that they read as two, not as one. */
23
+ const GAP = 3;
24
+ /** A left margin, so the art does not start against the edge of the screen. */
25
+ const INDENT = 2;
23
26
  const FONT = {
24
- A: [' ▒▒▒▒ ', '▒▒ ▒▒', '▒▒▒▒▒▒', '▒▒ ▒▒', '▒▒ ▒▒'],
25
- B: ['▒▒▒▒▒ ', '▒▒ ▒▒', '▒▒▒▒▒ ', '▒▒ ▒▒', '▒▒▒▒▒ '],
26
- C: [' ▒▒▒▒▒', '▒▒ ', '▒▒ ', '▒▒ ', ' ▒▒▒▒▒'],
27
- D: ['▒▒▒▒▒ ', '▒▒ ▒▒', '▒▒ ▒▒', '▒▒ ▒▒', '▒▒▒▒▒ '],
28
- E: ['▒▒▒▒▒▒', '▒▒ ', '▒▒▒▒▒ ', '▒▒ ', '▒▒▒▒▒▒'],
29
- F: ['▒▒▒▒▒▒', '▒▒ ', '▒▒▒▒▒ ', '▒▒ ', '▒▒ '],
30
- G: [' ▒▒▒▒▒', '▒▒ ', '▒▒ ▒▒▒', '▒▒ ▒▒', ' ▒▒▒▒▒'],
31
- H: ['▒▒ ▒▒', '▒▒ ▒▒', '▒▒▒▒▒▒', '▒▒ ▒▒', '▒▒ ▒▒'],
32
- I: ['▒▒▒▒▒▒', ' ▒▒ ', ' ▒▒ ', ' ▒▒ ', '▒▒▒▒▒▒'],
33
- J: ['▒▒▒▒▒▒', ' ▒▒ ', ' ▒▒ ', '▒▒ ▒▒ ', ' ▒▒▒ '],
34
- K: ['▒▒ ▒▒', '▒▒ ▒▒ ', '▒▒▒▒ ', '▒▒ ▒▒ ', '▒▒ ▒▒'],
35
- L: ['▒▒ ', '▒▒ ', '▒▒ ', '▒▒ ', '▒▒▒▒▒▒'],
36
- M: ['▒▒ ▒▒', '▒▒▒▒▒▒', '▒▒▒▒▒▒', '▒▒ ▒▒', '▒▒ ▒▒'],
37
- N: ['▒▒ ▒▒', '▒▒▒ ▒▒', '▒▒▒▒▒▒', '▒▒ ▒▒▒', '▒▒ ▒▒'],
38
- O: [' ▒▒▒▒ ', '▒▒ ▒▒', '▒▒ ▒▒', '▒▒ ▒▒', ' ▒▒▒▒ '],
39
- P: ['▒▒▒▒▒ ', '▒▒ ▒▒', '▒▒▒▒▒ ', '▒▒ ', '▒▒ '],
40
- Q: [' ▒▒▒▒ ', '▒▒ ▒▒', '▒▒ ▒▒', '▒▒ ▒▒ ', ' ▒▒ ▒▒'],
41
- R: ['▒▒▒▒▒ ', '▒▒ ▒▒', '▒▒▒▒▒ ', '▒▒ ▒▒ ', '▒▒ ▒▒'],
42
- S: [' ▒▒▒▒▒', '▒▒ ', ' ▒▒▒▒ ', ' ▒▒', '▒▒▒▒▒ '],
43
- T: ['▒▒▒▒▒▒', ' ▒▒ ', ' ▒▒ ', ' ▒▒ ', ' ▒▒ '],
44
- U: ['▒▒ ▒▒', '▒▒ ▒▒', '▒▒ ▒▒', '▒▒ ▒▒', ' ▒▒▒▒ '],
45
- V: ['▒▒ ▒▒', '▒▒ ▒▒', '▒▒ ▒▒', ' ▒▒▒▒ ', ' ▒▒ '],
46
- W: ['▒▒ ▒▒', '▒▒ ▒▒', '▒▒▒▒▒▒', '▒▒▒▒▒▒', '▒▒ ▒▒'],
47
- X: ['▒▒ ▒▒', ' ▒▒▒▒ ', ' ▒▒ ', ' ▒▒▒▒ ', '▒▒ ▒▒'],
48
- Y: ['▒▒ ▒▒', ' ▒▒▒▒ ', ' ▒▒ ', ' ▒▒ ', ' ▒▒ '],
49
- Z: ['▒▒▒▒▒▒', ' ▒▒ ', ' ▒▒ ', ' ▒▒ ', '▒▒▒▒▒▒'],
27
+ A: [' █████╗ ', '██╔══██╗', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
28
+ B: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██████╔╝', '╚═════╝ '],
29
+ C: [' ██████╗', '██╔════╝', '██║ ', '██║ ', '╚██████╗', ' ╚═════╝'],
30
+ D: ['██████╗ ', '██╔══██╗', '██║ ██║', '██║ ██║', '██████╔╝', '╚═════╝ '],
31
+ E: ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
32
+ F: ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '██║ ', '╚═╝ '],
33
+ G: [' ██████╗ ', '██╔════╝ ', '██║ ███╗', '██║ ██║', '╚██████╔╝', ' ╚═════╝ '],
34
+ H: ['██╗ ██╗', '██║ ██║', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
35
+ I: ['██╗', '██║', '██║', '██║', '██║', '╚═╝'],
36
+ J: [' ██╗', ' ██║', ' ██║', '██ ██║', '╚█████╔╝', ' ╚════╝ '],
37
+ K: ['██╗ ██╗', '██║ ██╔╝', '█████╔╝ ', '██╔═██╗ ', '██║ ██╗', '╚═╝ ╚═╝'],
38
+ L: ['██╗ ', '██║ ', '██║ ', '██║ ', '███████╗', '╚══════╝'],
39
+ M: ['███╗ ███╗', '████╗ ████║', '██╔████╔██║', '██║╚██╔╝██║', '██║ ╚═╝ ██║', '╚═╝ ╚═╝'],
40
+ N: ['███╗ ██╗', '████╗ ██║', '██╔██╗ ██║', '██║╚██╗██║', '██║ ╚████║', '╚═╝ ╚═══╝'],
41
+ O: [' ██████╗ ', '██╔═══██╗', '██║ ██║', '██║ ██║', '╚██████╔╝', ' ╚═════╝ '],
42
+ P: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔═══╝ ', '██║ ', '╚═╝ '],
43
+ Q: [' ██████╗ ', '██╔═══██╗', '██║ ██║', '██║▄▄ ██║', '╚██████╔╝', ' ╚══▀▀═╝ '],
44
+ R: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██║ ██║', '╚═╝ ╚═╝'],
45
+ S: ['███████╗', '██╔════╝', '███████╗', '╚════██║', '███████║', '╚══════╝'],
46
+ T: ['████████╗', '╚══██╔══╝', ' ██║ ', ' ██║ ', ' ██║ ', ' ╚═╝ '],
47
+ U: ['██╗ ██╗', '██║ ██║', '██║ ██║', '██║ ██║', '╚██████╔╝', ' ╚═════╝ '],
48
+ V: ['██╗ ██╗', '██║ ██║', '██║ ██║', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚═══╝ '],
49
+ W: ['██╗ ██╗', '██║ ██║', '██║ █╗ ██║', '██║███╗██║', '╚███╔███╔╝', ' ╚══╝╚══╝ '],
50
+ X: ['██╗ ██╗', '╚██╗██╔╝', ' ╚███╔╝ ', ' ██╔██╗ ', '██╔╝ ██╗', '╚═╝ ╚═╝'],
51
+ Y: ['██╗ ██╗', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚██╔╝ ', ' ██║ ', ' ╚═╝ '],
52
+ Z: ['███████╗', '╚══███╔╝', ' ███╔╝ ', ' ███╔╝ ', '███████╗', '╚══════╝'],
50
53
  };
51
- const BLANK = ' '.repeat(WIDTH);
52
- /** One word as five rows of equal length — so two words line up when joined. */
54
+ const BLANK = [' ', ' ', ' ', ' ', ' ', ' '];
55
+ /** One word as six rows of equal length — so two words line up when joined. */
53
56
  function big(word) {
54
57
  const rows = new Array(HEIGHT).fill('');
55
58
  for (const ch of word.toUpperCase()) {
56
- const glyph = FONT[ch];
59
+ const glyph = FONT[ch] ?? BLANK;
57
60
  for (let r = 0; r < HEIGHT; r++) {
58
- rows[r] += `${glyph?.[r] ?? BLANK} `;
61
+ rows[r] += glyph[r];
59
62
  }
60
63
  }
61
64
  return rows;
62
65
  }
63
66
  /** A wordmark rather than a sentence, so the letters are set apart. */
64
67
  const spaced = (s) => [...s.toUpperCase()].join(' ');
65
- /** The name, in the brightest thing the terminal has. */
66
- const bright = (s) => styleText(['bold', 'whiteBright'], s);
67
- /** The half that carries the colour. */
68
- const neon = (s) => styleText(['bold', 'magentaBright'], s);
68
+ /** Whether anything should be coloured at all — `styleText`'s own answer. */
69
+ const styling = () => styleText('dim', '.') !== '.';
70
+ const RESET = '\u001b[0m';
71
+ const HEAD = { face: '\u001b[1;38;5;231m', shade: '\u001b[0;38;5;244m' };
72
+ const ACCENT = { face: '\u001b[1;38;5;208m', shade: '\u001b[0;38;5;130m' };
73
+ /** Runs of stroke and runs of bevel, alternating; spaces stay unstyled. */
74
+ const RUNS = /[█▀▄]+|[^█▀▄ ]+/g;
75
+ const isStroke = (run) => /[█▀▄]/.test(run);
76
+ /**
77
+ * Two tones per word — without the darker bevel the letter is a flat outline
78
+ * and the shadow reads as part of the stroke.
79
+ */
80
+ const paint = (text, tone) => styling()
81
+ ? text.replace(RUNS, (run) => `${isStroke(run) ? tone.face : tone.shade}${run}${RESET}`)
82
+ : text;
83
+ /** The wordmark has no bevel to shade, so it is all face. */
84
+ const flat = (text, tone) => styling() ? `${tone.face}${text}${RESET}` : text;
69
85
  export const NEO_BANNER = {
70
86
  head: 'Zenera',
71
87
  accent: 'Neo',
@@ -76,17 +92,23 @@ export function bannerLines(text, columns = process.stderr.columns || 80) {
76
92
  // Trimmed: the widest accent row is the banner's right edge, and a pad left
77
93
  // inside a styled string cannot be trimmed away later.
78
94
  const accent = big(text.accent).map((row) => row.trimEnd());
79
- const width = 2 + head[0].length + Math.max(...accent.map((row) => row.length));
80
- const foot = ` ${dim(spaced(text.subtitle))}`;
95
+ const margin = ' '.repeat(INDENT);
96
+ const width = INDENT + head[0].length + GAP + Math.max(...accent.map((row) => row.length));
81
97
  if (width > columns) {
82
- return [` ${bright(text.head.toUpperCase())} ${neon(text.accent.toUpperCase())}`, foot];
98
+ return [
99
+ `${margin}${flat(text.head.toUpperCase(), HEAD)} ${flat(text.accent.toUpperCase(), ACCENT)}`,
100
+ `${margin}${dim(spaced(text.subtitle))}`,
101
+ ];
83
102
  }
84
103
  const lines = [];
85
104
  for (let r = 0; r < HEIGHT; r++) {
86
- lines.push(` ${bright(head[r])} ${neon(accent[r])}`);
105
+ lines.push(`${margin}${paint(head[r], HEAD)}${' '.repeat(GAP)}${paint(accent[r], ACCENT)}`);
87
106
  }
107
+ // Rules on both sides, centred under the art: the subtitle is a caption,
108
+ // and without them a short line of spaced capitals floats.
109
+ const caption = `─── ${spaced(text.subtitle)} ───`;
88
110
  lines.push('');
89
- lines.push(foot);
111
+ lines.push(`${' '.repeat(Math.max(INDENT, Math.round((width - caption.length) / 2)))}${dim(caption)}`);
90
112
  return lines;
91
113
  }
92
114
  /** Narration, and only for someone watching. */
@@ -4,6 +4,7 @@ import { init } from "./init.js";
4
4
  import { inspect } from "./inspect.js";
5
5
  import { key } from "./key.js";
6
6
  import { list } from "./list.js";
7
+ import { memory } from "./memory.js";
7
8
  import { models } from "./models.js";
8
9
  import { open } from "./open.js";
9
10
  import { run } from "./run.js";
@@ -23,6 +24,7 @@ export const COMMANDS = {
23
24
  models,
24
25
  check,
25
26
  inspect,
27
+ memory,
26
28
  sandbox,
27
29
  cache,
28
30
  version,
@@ -36,6 +38,7 @@ export const ALIASES = {
36
38
  validate: 'check',
37
39
  doctor: 'check',
38
40
  report: 'inspect',
41
+ mem: 'memory',
39
42
  edit: 'open',
40
43
  code: 'open',
41
44
  mock: 'faker',
@@ -96,10 +96,14 @@ export const init = {
96
96
  summary: 'Create a project here, or in <dir>, and register it.',
97
97
  usage: USAGE,
98
98
  details: [
99
- 'Writes INSTRUCTIONS.md, agents.yaml and agents/, then records the',
100
- 'directory so `zen list` and `zen open` can find it by name. Editor files',
101
- '(.vscode/settings.json and the .github/ tree) are written',
102
- 'alongside, replacing any already there.',
99
+ 'Writes SPECIFICATION.md, INSTRUCTIONS.md, agents.yaml, agents/ and',
100
+ 'scripts/, then records the directory so `zen list` and `zen open` can',
101
+ 'find it by name. Editor files (.vscode/settings.json and the .github/',
102
+ 'tree) are written alongside, replacing any already there.',
103
+ '',
104
+ 'SPECIFICATION.md states what the project is for and what it may do,',
105
+ 'and describes the one that was just written. Say what you are building',
106
+ 'there, then run /sync-with-spec in your editor to make the rest match.',
103
107
  '',
104
108
  'The default agent gets the file tools and a sandboxed shell, plus',
105
109
  '`exa:*` when the keyring holds an Exa key. Without --model, the',
@@ -1,9 +1,9 @@
1
+ import { assertState, buildRunReport, FilePayloadStore, memoryDir, MemoryStore, PayloadResolver, readProjectConfig, renderReportHtml, } from '@zenera/neo';
1
2
  import { spawn } from 'node:child_process';
2
3
  import { createReadStream, existsSync } from 'node:fs';
3
4
  import { readFile, writeFile } from 'node:fs/promises';
4
5
  import { createServer } from 'node:http';
5
- import { extname, normalize, resolve, sep } from 'node:path';
6
- import { assertState, buildRunReport, FilePayloadStore, PayloadResolver, renderReportHtml, } from '@zenera/neo';
6
+ import { extname, join, normalize, resolve, sep } from 'node:path';
7
7
  import { parse } from "../args.js";
8
8
  import { project as resolveProject } from "../resolve.js";
9
9
  import { display, newestRun, newestSession, requireSession, runPaths, sessionPaths, } from "../session.js";
@@ -29,7 +29,7 @@ export const inspect = {
29
29
  const session = pickSession(dir, values.session);
30
30
  const run = pickRun(session, positionals[0]);
31
31
  if (values.rebuild || !existsSync(run.report)) {
32
- await rebuild(session, run);
32
+ await rebuild(session, run, await memory(dir));
33
33
  }
34
34
  if (ctx.json) {
35
35
  json({ session: session.id, run: run.id, report: run.report });
@@ -78,7 +78,7 @@ function pickRun(session, asked) {
78
78
  * state — which is what makes `--rebuild` safe and what makes an old run
79
79
  * readable by a newer renderer.
80
80
  */
81
- async function rebuild(session, run) {
81
+ async function rebuild(session, run, store) {
82
82
  if (!existsSync(run.state)) {
83
83
  throw invalidError(`run ${run.id} has no state to rebuild from`);
84
84
  }
@@ -90,9 +90,27 @@ async function rebuild(session, run) {
90
90
  throw invalidError(`${run.state}: ${err instanceof Error ? err.message : String(err)}`);
91
91
  }
92
92
  const payloads = new PayloadResolver(new FilePayloadStore({ dir: session.blobs, id: 'file' }));
93
- const report = await buildRunReport(state, payloads, { title: run.id });
93
+ const report = await buildRunReport(state, payloads, { title: run.id, memory: store });
94
94
  await writeFile(run.report, renderReportHtml(report), 'utf8');
95
95
  }
96
+ /**
97
+ * The project's memory, unlocked, when it has one. Without it the memory view
98
+ * has the shape of what the run recalled but not a word of it; a run that
99
+ * never touched memory pays nothing, because the report asks for no node.
100
+ */
101
+ async function memory(dir) {
102
+ const { config } = readProjectConfig(dir);
103
+ const at = memoryDir(dir, config);
104
+ if (!at || !existsSync(join(at, 'manifest.json'))) {
105
+ return undefined;
106
+ }
107
+ try {
108
+ return await MemoryStore.open(at, { lock: false });
109
+ }
110
+ catch {
111
+ return undefined;
112
+ }
113
+ }
96
114
  // ---------------------------------------------------------------------------
97
115
  // Serving
98
116
  //
@@ -0,0 +1,3 @@
1
+ import type { Command } from '../command.ts';
2
+ export declare const memory: Command;
3
+ //# sourceMappingURL=memory.d.ts.map