picopilot 0.2.0 → 0.2.1

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.
@@ -1,5 +1,5 @@
1
1
  import { z } from 'incur';
2
- import { DEFAULT_DISPLAY_MODE, isDisplayMode, pico8ChildEnv, ShellPico8Adapter, } from '../engine/pico8/index.js';
2
+ import { DEFAULT_DISPLAY_MODE, DISPLAY_MODES, isDisplayMode, pico8ChildEnv, ShellPico8Adapter, } from '../engine/pico8/index.js';
3
3
  /**
4
4
  * The env schema every PICO-8-gated command declares. These are the values
5
5
  * picopilot itself RESOLVES (incur reads them from its env source, which tests
@@ -28,9 +28,20 @@ export function createShellAdapterFactory(deps = {}) {
28
28
  });
29
29
  }
30
30
  /**
31
- * The display mode a command handler sees. The root middleware in `createCli`
32
- * resolves it (flag > env > config > default) into the `display` var; a command
33
- * mounted on a bare CLI (some tests) has no var and gets the default.
31
+ * The `display` option of the commands that use the display mode (`run`,
32
+ * `audio record`, `audio render`, `doctor`). A per-command option, not an incur
33
+ * global, because incur's MCP transport passes no globals: as an option it is
34
+ * also an argument of the MCP tool. Unset = the invocation-level value.
35
+ */
36
+ export const DISPLAY_OPTION = z
37
+ .enum(DISPLAY_MODES)
38
+ .optional()
39
+ .describe('How a PICO-8 -run session (audio recording) gets a display: auto (default: a private virtual display only when there is none), xvfb (always virtual), native (as-is). Else PICOPILOT_DISPLAY, else picopilot.json "display".');
40
+ /**
41
+ * The display mode the root middleware in `createCli` resolved for this
42
+ * invocation (top-level `--display` > PICOPILOT_DISPLAY > picopilot.json >
43
+ * auto). A command mounted on a bare CLI (some tests) has no var and gets the
44
+ * default.
34
45
  */
35
46
  export function displayModeOf(vars) {
36
47
  const value = vars !== null && typeof vars === 'object'
@@ -38,6 +49,42 @@ export function displayModeOf(vars) {
38
49
  : undefined;
39
50
  return isDisplayMode(value) ? value : DEFAULT_DISPLAY_MODE;
40
51
  }
52
+ /**
53
+ * The display mode a command uses: its own `display` option (a per-call MCP
54
+ * argument) wins, else the invocation-level value. An invalid invocation-level
55
+ * value (a typo in PICOPILOT_DISPLAY, say) is an `invalid-display-mode` error
56
+ * here, so only the commands that use the mode fail on it.
57
+ */
58
+ export function resolveCommandDisplay(option, vars) {
59
+ if (option !== undefined)
60
+ return { ok: true, mode: option };
61
+ const problem = vars !== null && typeof vars === 'object'
62
+ ? vars.displayError
63
+ : undefined;
64
+ if (typeof problem === 'string') {
65
+ return {
66
+ ok: false,
67
+ error: { code: 'invalid-display-mode', message: problem, exitCode: 1 },
68
+ };
69
+ }
70
+ return { ok: true, mode: displayModeOf(vars) };
71
+ }
72
+ /**
73
+ * Under `--mcp`, incur loads no config file, so picopilot loads picopilot.json
74
+ * itself and hands the tree to commands as the `mcpConfig` var. A command whose
75
+ * option can come from the config reads its fallback here (CLI runs get it from
76
+ * incur as the option's default, so this is undefined there).
77
+ */
78
+ export function mcpConfigOption(vars, commandPath, key) {
79
+ let node = vars !== null && typeof vars === 'object'
80
+ ? vars.mcpConfig
81
+ : undefined;
82
+ for (const part of commandPath) {
83
+ node = node
84
+ ?.commands?.[part];
85
+ }
86
+ return node?.options?.[key];
87
+ }
41
88
  /**
42
89
  * The `error(...)` payload for a `display-unavailable` result (ADR-0017): the
43
90
  * session needs a display, there is none, and no Xvfb is installed to provide a
@@ -1 +1 @@
1
- {"version":3,"file":"pico8-env.js","sourceRoot":"","sources":["../../src/commands/pico8-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,OAAO,CAAC;AACxB,OAAO,EACN,oBAAoB,EAEpB,aAAa,EAEb,aAAa,EACb,iBAAiB,GAGjB,MAAM,0BAA0B,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,UAAU,EAAE,CAAC;SACX,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,4DAA4D,CAAC;IACxE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;CAC/D,CAAC,CAAC;AA4BH;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CACxC,OAAgC,EAAE;IAElC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CACvB,IAAI,iBAAiB,CAAC;QACrB,GAAG,EAAE,aAAa,CACjB,GAAyC,EACzC,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAC7B;QACD,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,oBAAoB;QACjD,cAAc,EAAE,IAAI,CAAC,cAAc;KACnC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa;IAC1C,MAAM,KAAK,GACV,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QACxC,CAAC,CAAE,IAA4B,CAAC,OAAO;QACvC,CAAC,CAAC,SAAS,CAAC;IACd,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,oBAAoB,CAAC;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACtC,IAAY,EACZ,MAAwC;IAExC,OAAO;QACN,IAAI,EAAE,qBAA8B;QACpC,OAAO,EAAE,GAAG,IAAI,uDAAuD,MAAM,CAAC,MAAM,aAAa,MAAM,CAAC,MAAM,GAAG;QACjH,QAAQ,EAAE,CAAC;QACX,GAAG,EAAE;YACJ,WAAW,EAAE,gDAAgD,MAAM,CAAC,MAAM,uHAAuH;YACjM,QAAQ,EAAE;gBACT;oBACC,OAAO,EAAE,QAAQ;oBACjB,WAAW,EACV,0FAA0F;iBAC3F;aACD;SACD;KACD,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,kBAAkB,CAAC,MAGlC;IACA,OAAO;QACN,IAAI,EAAE,iBAA0B;QAChC,OAAO,EAAE,4BAA4B,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QACxF,QAAQ,EAAE,CAAC;KACX,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"pico8-env.js","sourceRoot":"","sources":["../../src/commands/pico8-env.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAOlC,OAAO,EAMN,KAAK,mBAAmB,EACxB,MAAM,gBAAgB,CAAC;AAExB;;;;GAIG;AACH,YAAY,EAAC,mBAAmB,EAAC,MAAM,gBAAgB,CAAC;AAOxD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAC1B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,mBAA2C,GACzD,IAAI,CAuQN"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAOlC,OAAO,EAON,KAAK,mBAAmB,EACxB,MAAM,gBAAgB,CAAC;AAExB;;;;GAIG;AACH,YAAY,EAAC,mBAAmB,EAAC,MAAM,gBAAgB,CAAC;AAOxD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAC1B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,mBAA2C,GACzD,IAAI,CA4QN"}
@@ -4,7 +4,7 @@ import { isAbsolute, join, resolve } from 'node:path';
4
4
  import { z } from 'incur';
5
5
  import { buildRecordHarness, DONE_SENTINEL, HarnessError, } from '../engine/pico8/index.js';
6
6
  import { makeReadInclude } from './cart-includes.js';
7
- import { createShellAdapterFactory, displayModeOf, displayUnavailableError, PICO8_ENV, pico8NotFoundError, } from './pico8-env.js';
7
+ import { createShellAdapterFactory, DISPLAY_OPTION, displayUnavailableError, PICO8_ENV, pico8NotFoundError, resolveCommandDisplay, } from './pico8-env.js';
8
8
  const defaultAdapterFactory = createShellAdapterFactory();
9
9
  /** The default backstop: kill a cart that never signals/exits after this many ms. */
10
10
  const DEFAULT_BACKSTOP_MS = 15_000;
@@ -55,6 +55,7 @@ export function registerRun(cli, adapterFactory = defaultAdapterFactory) {
55
55
  .string()
56
56
  .optional()
57
57
  .describe('One-shot scripted input passed as `-p` (the cart reads it via stat(6)) for automated playtests.'),
58
+ display: DISPLAY_OPTION,
58
59
  recordAudio: z
59
60
  .boolean()
60
61
  .default(false)
@@ -125,8 +126,13 @@ export function registerRun(cli, adapterFactory = defaultAdapterFactory) {
125
126
  : mkdtempSync(join(tmpdir(), 'picopilot-run-'));
126
127
  if (!existsSync(shotDir))
127
128
  mkdirSync(shotDir, { recursive: true });
129
+ // The display mode only matters to the --record-audio (-run) pass, so a
130
+ // bad PICOPILOT_DISPLAY only fails a run that records.
131
+ const display = resolveCommandDisplay(options.display, vars);
132
+ if (options.recordAudio && !display.ok)
133
+ return error(display.error);
128
134
  const adapter = adapterFactory(env, {
129
- display: displayModeOf(vars),
135
+ display: display.ok ? display.mode : 'auto',
130
136
  });
131
137
  const result = await adapter.run({
132
138
  cartPath,
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picopilot",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "An agent-first toolchain for PICO-8 game development, built on the incur CLI framework.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -31,7 +31,7 @@
31
31
  "node": ">=22"
32
32
  },
33
33
  "dependencies": {
34
- "incur": "0.4.10"
34
+ "incur": "0.7.1"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "22.12.0",
package/src/cli.test.ts CHANGED
@@ -70,6 +70,7 @@ describe('display mode resolution through the CLI', () => {
70
70
  for (const argv of [
71
71
  ['--display', 'native', 'probe-display'],
72
72
  ['probe-display', '--display', 'native'],
73
+ ['probe-display', '--display=native'],
73
74
  ]) {
74
75
  const {out} = await probe(
75
76
  argv,
@@ -84,7 +85,7 @@ describe('display mode resolution through the CLI', () => {
84
85
  const cli = createCli();
85
86
  let stdout = '';
86
87
  let exitCode = 0;
87
- await cli.serve(['run', join(dir, 'none.p8'), '--json'], {
88
+ await cli.serve(['doctor', '--json'], {
88
89
  stdout(s) {
89
90
  stdout += s;
90
91
  },
@@ -99,6 +100,46 @@ describe('display mode resolution through the CLI', () => {
99
100
  expect(out.message).toContain('PICOPILOT_DISPLAY');
100
101
  });
101
102
 
103
+ it('an invalid --display is an error for a display command', async () => {
104
+ const cli = createCli();
105
+ let stdout = '';
106
+ let exitCode = 0;
107
+ await cli.serve(['doctor', '--display', 'bogus', '--json'], {
108
+ stdout(s) {
109
+ stdout += s;
110
+ },
111
+ exit(code) {
112
+ exitCode = code;
113
+ },
114
+ env: {},
115
+ });
116
+ expect(exitCode).not.toBe(0);
117
+ expect(stdout).toContain('--display');
118
+ });
119
+
120
+ it('a --display with no value is rejected, and does not eat the next flag', async () => {
121
+ for (const argv of [
122
+ ['doctor', '--display', '--json'],
123
+ ['doctor', '--json', '--display'],
124
+ ]) {
125
+ const cli = createCli();
126
+ let stdout = '';
127
+ let exitCode = 0;
128
+ await cli.serve(argv, {
129
+ stdout(s) {
130
+ stdout += s;
131
+ },
132
+ exit(code) {
133
+ exitCode = code;
134
+ },
135
+ env: {},
136
+ });
137
+ expect(exitCode, argv.join(' ')).not.toBe(0);
138
+ // --json still applied: the error envelope is JSON.
139
+ expect(() => JSON.parse(stdout), argv.join(' ')).not.toThrow();
140
+ }
141
+ });
142
+
102
143
  it('an invalid value does not break commands that never use the display', async () => {
103
144
  const cli = createCli();
104
145
  let stdout = '';
package/src/cli.ts CHANGED
@@ -1,4 +1,6 @@
1
- import {readFileSync} from 'node:fs';
1
+ import {existsSync, readFileSync} from 'node:fs';
2
+ import {homedir} from 'node:os';
3
+ import {join, resolve} from 'node:path';
2
4
  import {Cli, z} from 'incur';
3
5
  import {registerAudio} from './commands/audio.js';
4
6
  import {registerDoctor} from './commands/doctor.js';
@@ -24,13 +26,88 @@ import {
24
26
  } from './engine/pico8/display.js';
25
27
  import {VERSION} from './version.js';
26
28
 
27
- /** The commands that use the display mode (they start a `-run` session, or report on it). */
28
- const DISPLAY_COMMANDS: ReadonlySet<string> = new Set([
29
- 'run',
30
- 'audio record',
31
- 'audio render',
32
- 'doctor',
33
- ]);
29
+ /** The config files picopilot searches (first existing wins), relative to the cwd. */
30
+ const CONFIG_FILES = ['picopilot.json'];
31
+
32
+ /**
33
+ * What one `serve` invocation said at the top level, captured BEFORE incur sees
34
+ * the argv: the `--display` flag (taken out of argv, so it works before or after
35
+ * the command name and on `--mcp`), and for `--mcp` the config flags. incur's
36
+ * MCP transport passes no global flags to a tool call and loads no config file,
37
+ * so picopilot carries both itself.
38
+ */
39
+ interface Invocation {
40
+ display?: string;
41
+ mcp: boolean;
42
+ /** MCP only: the config tree picopilot loaded itself (undefined = none). */
43
+ mcpConfig?: Record<string, unknown>;
44
+ }
45
+
46
+ /**
47
+ * Removes every `--display <v>` / `--display=<v>` from argv and returns the last
48
+ * value. A `--display` with no value (last token, or followed by another flag)
49
+ * stays in argv, so incur rejects it instead of it silently eating the next flag.
50
+ */
51
+ function takeDisplayFlag(argv: string[]): {argv: string[]; display?: string} {
52
+ const rest: string[] = [];
53
+ let display: string | undefined;
54
+ for (let i = 0; i < argv.length; i++) {
55
+ const a = argv[i] as string;
56
+ const next = argv[i + 1];
57
+ if (a === '--display' && next !== undefined && !next.startsWith('-')) {
58
+ display = next;
59
+ i++;
60
+ } else if (a.startsWith('--display=') && a.length > '--display='.length) {
61
+ display = a.slice('--display='.length);
62
+ } else {
63
+ rest.push(a);
64
+ }
65
+ }
66
+ return {argv: rest, display};
67
+ }
68
+
69
+ /** Resolves a config path the way incur does: `~` is the home dir, else relative to the cwd. */
70
+ function resolveConfigPath(path: string): string {
71
+ return path === '~' || path.startsWith('~/')
72
+ ? join(homedir(), path.slice(1))
73
+ : resolve(path);
74
+ }
75
+
76
+ /**
77
+ * For `--mcp`: the config file incur would have loaded on the CLI. Mirrors
78
+ * incur's own parsing: `--config <p>` / `--config=<p>` / `--no-config`, the last
79
+ * one wins, a missing value is an error; else the first existing default file.
80
+ */
81
+ function loadMcpConfig(argv: string[]): Record<string, unknown> | undefined {
82
+ let explicit: string | undefined;
83
+ let disabled = false;
84
+ for (let i = 0; i < argv.length; i++) {
85
+ const a = argv[i] as string;
86
+ let value: string | undefined;
87
+ if (a === '--config') value = argv[++i];
88
+ else if (a.startsWith('--config=')) value = a.slice('--config='.length);
89
+ else if (a === '--no-config') {
90
+ explicit = undefined;
91
+ disabled = true;
92
+ continue;
93
+ } else continue;
94
+ if (value === undefined || value === '') {
95
+ throw new Error('Missing value for flag: --config');
96
+ }
97
+ explicit = value;
98
+ disabled = false;
99
+ }
100
+ if (disabled) return undefined;
101
+ const sink: {display?: unknown} = {};
102
+ if (explicit !== undefined) {
103
+ return loadConfig(resolveConfigPath(explicit), sink);
104
+ }
105
+ for (const file of CONFIG_FILES) {
106
+ const path = resolveConfigPath(file);
107
+ if (existsSync(path)) return loadConfig(path, sink);
108
+ }
109
+ return undefined;
110
+ }
34
111
 
35
112
  /**
36
113
  * Reads a picopilot.json for incur's config layer AND captures its top-level
@@ -91,20 +168,12 @@ export function createCli(): Cli.Cli {
91
168
  // Per-cart policy config (e.g. `allowMapOverlap`); precedence argv > config > defaults.
92
169
  config: {
93
170
  flag: 'config',
94
- files: ['picopilot.json'],
171
+ files: CONFIG_FILES,
95
172
  loader: async (path) => loadConfig(path, configSink),
96
173
  },
97
- // The display mode (ADR-0017) is global: it applies to every command that
98
- // starts a PICO-8 `-run` session. Precedence: --display > PICOPILOT_DISPLAY
99
- // > picopilot.json `display` > auto.
100
- globals: z.object({
101
- display: z
102
- .enum(DISPLAY_MODES)
103
- .optional()
104
- .describe(
105
- 'How PICO-8 -run sessions (audio record/render, run --record-audio) get a display: auto (default: a private virtual display only when there is none), xvfb (always virtual), native (as-is). Also PICOPILOT_DISPLAY or picopilot.json "display" (under --mcp, only PICOPILOT_DISPLAY applies).',
106
- ),
107
- }),
174
+ // The display mode (ADR-0017): `--display` (a per-command option on the
175
+ // commands that use it, also accepted anywhere on the command line and on
176
+ // `--mcp`) > PICOPILOT_DISPLAY > picopilot.json `display` > auto.
108
177
  env: z.object({
109
178
  [DISPLAY_ENV_VAR]: z
110
179
  .string()
@@ -113,37 +182,74 @@ export function createCli(): Cli.Cli {
113
182
  }),
114
183
  vars: z.object({
115
184
  display: z.enum(DISPLAY_MODES).default(DEFAULT_DISPLAY_MODE),
185
+ displayError: z.string().optional(),
186
+ mcpConfig: z.record(z.string(), z.unknown()).optional(),
116
187
  }),
188
+ mcp: {
189
+ // One MCP tool per command (incur's default is progressive discovery
190
+ // behind search/call meta-tools); picopilot's surface is small, and the
191
+ // skills name commands directly.
192
+ tools: {discovery: 'direct'},
193
+ },
117
194
  });
118
195
 
196
+ const invocation: Invocation = {mcp: false};
197
+
119
198
  cli.use(async (c, next) => {
120
- const configured = configSink.display;
199
+ const configured = invocation.mcp
200
+ ? invocation.mcpConfig?.[DISPLAY_CONFIG_KEY]
201
+ : configSink.display;
121
202
  configSink.display = undefined; // never leak into a later invocation
122
203
  const resolved = resolveDisplayMode({
123
- // incur's MCP transport passes no globals (and loads no config file), so
124
- // under `--mcp` only PICOPILOT_DISPLAY applies.
125
- flag: c.globals?.display,
204
+ flag: invocation.display,
126
205
  env: (c.env as Record<string, string | undefined>)[DISPLAY_ENV_VAR],
127
206
  config: configured,
128
207
  });
129
- if (!resolved.ok) {
130
- // A bad value only fails the commands that use it; the rest run as usual.
131
- if (!DISPLAY_COMMANDS.has(c.command)) return next();
132
- // (An invalid --display never gets here: incur's enum rejects it first.)
208
+ if (resolved.ok) {
209
+ c.set('display', resolved.mode);
210
+ } else {
211
+ // Not an error here: only the commands that USE the display mode fail
212
+ // on a bad value (see resolveCommandDisplay); the rest run as usual.
133
213
  const where =
134
- resolved.source === 'env'
135
- ? DISPLAY_ENV_VAR
136
- : `picopilot.json "${DISPLAY_CONFIG_KEY}"`;
137
- return c.error({
138
- code: 'invalid-display-mode',
139
- message: `${where} is "${resolved.value}"; expected one of: ${DISPLAY_MODES.join(', ')}.`,
140
- exitCode: 1,
141
- });
214
+ resolved.source === 'flag'
215
+ ? '--display'
216
+ : resolved.source === 'env'
217
+ ? DISPLAY_ENV_VAR
218
+ : `picopilot.json "${DISPLAY_CONFIG_KEY}"`;
219
+ c.set(
220
+ 'displayError',
221
+ `${where} is "${resolved.value}"; expected one of: ${DISPLAY_MODES.join(', ')}.`,
222
+ );
223
+ }
224
+ if (invocation.mcp && invocation.mcpConfig !== undefined) {
225
+ c.set('mcpConfig', invocation.mcpConfig);
142
226
  }
143
- c.set('display', resolved.mode);
144
227
  await next();
145
228
  });
146
229
 
230
+ // Capture the top-level `--display` (and, for `--mcp`, the config) before
231
+ // incur parses argv; see {@link Invocation}.
232
+ const serve = cli.serve.bind(cli);
233
+ cli.serve = async (argv = process.argv.slice(2), options) => {
234
+ const taken = takeDisplayFlag(argv);
235
+ invocation.display = taken.display;
236
+ invocation.mcp = taken.argv.includes('--mcp');
237
+ invocation.mcpConfig = undefined;
238
+ if (invocation.mcp) {
239
+ try {
240
+ invocation.mcpConfig = loadMcpConfig(taken.argv);
241
+ } catch (e) {
242
+ // A broken config must stop the server visibly, not serve without it.
243
+ process.stderr.write(
244
+ `picopilot --mcp: ${e instanceof Error ? e.message : String(e)}\n`,
245
+ );
246
+ process.exitCode = 1;
247
+ return;
248
+ }
249
+ }
250
+ return serve(taken.argv, options);
251
+ };
252
+
147
253
  // The register* helpers take the untyped Cli: the display var is read through
148
254
  // `displayModeOf`, so they do not depend on this CLI's generics.
149
255
  const root = cli as unknown as Cli.Cli;
@@ -20,11 +20,12 @@ import {
20
20
  import {makeReadInclude} from './cart-includes.js';
21
21
  import {
22
22
  createShellAdapterFactory,
23
- displayModeOf,
23
+ DISPLAY_OPTION,
24
24
  displayUnavailableError,
25
25
  PICO8_ENV,
26
26
  type Pico8AdapterFactory,
27
27
  pico8NotFoundError,
28
+ resolveCommandDisplay,
28
29
  } from './pico8-env.js';
29
30
 
30
31
  /**
@@ -219,6 +220,7 @@ function buildAudioGroup(adapterFactory: Pico8AdapterFactory): Cli.Cli {
219
220
  .describe(
220
221
  'Safety backstop: kill PICO-8 after this many ms if it never signals.',
221
222
  ),
223
+ display: DISPLAY_OPTION,
222
224
  }),
223
225
  env: AUDIO_ENV,
224
226
  output: AUDIO_OUTPUT,
@@ -262,8 +264,10 @@ function buildAudioGroup(adapterFactory: Pico8AdapterFactory): Cli.Cli {
262
264
  const harnessCart = join(wavDir, 'record-harness.p8');
263
265
  writeFileSync(harnessCart, harness.cartText);
264
266
 
267
+ const display = resolveCommandDisplay(options.display, vars);
268
+ if (!display.ok) return error(display.error);
265
269
  const adapter = adapterFactory(env as NodeJS.ProcessEnv, {
266
- display: displayModeOf(vars),
270
+ display: display.mode,
267
271
  });
268
272
  const result = await adapter.record({
269
273
  cartPath: harnessCart,
@@ -338,6 +342,7 @@ function buildAudioGroup(adapterFactory: Pico8AdapterFactory): Cli.Cli {
338
342
  .describe(
339
343
  'Safety backstop: kill PICO-8 after this many ms if it never signals.',
340
344
  ),
345
+ display: DISPLAY_OPTION,
341
346
  }),
342
347
  env: AUDIO_ENV,
343
348
  output: AUDIO_OUTPUT,
@@ -394,8 +399,10 @@ function buildAudioGroup(adapterFactory: Pico8AdapterFactory): Cli.Cli {
394
399
  const harnessCart = join(wavDir, 'render-harness.p8');
395
400
  writeFileSync(harnessCart, harness.cartText);
396
401
 
402
+ const display = resolveCommandDisplay(options.display, vars);
403
+ if (!display.ok) return error(display.error);
397
404
  const adapter = adapterFactory(env as NodeJS.ProcessEnv, {
398
- display: displayModeOf(vars),
405
+ display: display.mode,
399
406
  });
400
407
  const result = await adapter.record({
401
408
  cartPath: harnessCart,
@@ -15,8 +15,9 @@ import {
15
15
  } from '../engine/pico8/index.js';
16
16
  import {
17
17
  createShellAdapterFactory,
18
- displayModeOf,
18
+ DISPLAY_OPTION,
19
19
  PICO8_ENV,
20
+ resolveCommandDisplay,
20
21
  type Pico8AdapterFactory,
21
22
  } from './pico8-env.js';
22
23
  import {
@@ -112,6 +113,7 @@ export function registerDoctor(cli: Cli.Cli, deps: DoctorDeps = {}): void {
112
113
  description:
113
114
  'Report what this machine can do: PICO-8 (found, runs headless, version), shrinko8, the display a PICO-8 -run session would use (native / virtual Xvfb / none), and which picopilot commands are therefore available. Call this first on a new machine.',
114
115
  env: PICO8_ENV,
116
+ options: z.object({display: DISPLAY_OPTION}),
115
117
  output: z.object({
116
118
  pico8: z.object({
117
119
  found: z.boolean().describe('Whether the PICO-8 binary was found.'),
@@ -162,8 +164,10 @@ export function registerDoctor(cli: Cli.Cli, deps: DoctorDeps = {}): void {
162
164
  )
163
165
  .describe('Commands that do not, and what they are missing.'),
164
166
  }),
165
- async run({env, ok, var: vars}) {
166
- const display: DisplayMode = displayModeOf(vars);
167
+ async run({env, ok, error, options, var: vars}) {
168
+ const resolved = resolveCommandDisplay(options.display, vars);
169
+ if (!resolved.ok) return error(resolved.error);
170
+ const display: DisplayMode = resolved.mode;
167
171
  const childEnv = pico8ChildEnv(
168
172
  env as Record<string, string | undefined>,
169
173
  deps.parentEnv ?? process.env,
@@ -25,6 +25,7 @@ import {
25
25
  createShellShrinkoFactory,
26
26
  type ShrinkoAdapterFactory,
27
27
  } from './shrinko-env.js';
28
+ import {mcpConfigOption} from './pico8-env.js';
28
29
 
29
30
  /** Injects the shrinko adapter `gfx import`/`gfx export` use (the spritesheet round-trip). */
30
31
  export type {ShrinkoAdapterFactory} from './shrinko-env.js';
@@ -199,11 +200,13 @@ function buildGfxGroup(shrinkoFactory: ShrinkoAdapterFactory): Cli.Cli {
199
200
  ),
200
201
  }),
201
202
  options: z.object({
203
+ // No zod default: an absent value must stay distinguishable, so that under
204
+ // `--mcp` (where incur loads no config) picopilot.json still applies.
202
205
  allowMapOverlap: z
203
206
  .boolean()
204
- .default(false)
207
+ .optional()
205
208
  .describe(
206
- 'Authorise overwriting existing shared-region data when writing sprites 128-255 (ADR-0004). Also settable as allowMapOverlap in picopilot.json.',
209
+ 'Authorise overwriting existing shared-region data when writing sprites 128-255 (ADR-0004). Also settable as allowMapOverlap in picopilot.json. Default false.',
207
210
  ),
208
211
  }),
209
212
  output: z.object({
@@ -237,7 +240,7 @@ function buildGfxGroup(shrinkoFactory: ShrinkoAdapterFactory): Cli.Cli {
237
240
  },
238
241
  },
239
242
  ],
240
- run({args, options, error, ok}) {
243
+ run({args, options, error, ok, var: vars}) {
241
244
  const cartPath = resolveCart(args.cart);
242
245
 
243
246
  // Decode the incoming grid FIRST: a malformed grid is a caller error and
@@ -267,12 +270,12 @@ function buildGfxGroup(shrinkoFactory: ShrinkoAdapterFactory): Cli.Cli {
267
270
  // (the shared-bank bytes at risk), NOT __map__ (ADR-0004). The flag and
268
271
  // the picopilot.json config both feed `options.allowMapOverlap` (incur
269
272
  // resolves argv > config > default), so both authorisation paths converge
270
- // here.
271
- const decision = decideOverlap(
272
- sheet,
273
- args.sprite,
274
- options.allowMapOverlap,
275
- );
273
+ // here. Under `--mcp` incur loads no config, so the picopilot.json value
274
+ // comes from the `mcpConfig` var instead.
275
+ const allowMapOverlap =
276
+ options.allowMapOverlap ??
277
+ mcpConfigOption(vars, ['gfx', 'set'], 'allowMapOverlap') === true;
278
+ const decision = decideOverlap(sheet, args.sprite, allowMapOverlap);
276
279
 
277
280
  if (decision.kind === 'refused') {
278
281
  // The silent-corruption guard: REFUSE and DO NOT write. The cart file is