0xray 4.0.31 → 4.0.32

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,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.31",
3
+ "version": "4.0.32",
4
4
  "description": "0xRay suit for Grok, Hermes, OpenClaw, OpenCode: mill+inspect, Codex, 7 MCP servers. Costume 45/42 is opt-in foundry, not the default wear.",
5
5
  "author": {
6
6
  "name": "0xRay AI",
package/.mcp.json CHANGED
@@ -34,6 +34,14 @@
34
34
  "repertoire": {
35
35
  "command": "node",
36
36
  "args": ["scripts/mjs/run-repertoire-mcp.mjs"]
37
+ },
38
+ "goggles": {
39
+ "command": "node",
40
+ "args": ["scripts/mjs/run-goggles-mcp.mjs"],
41
+ "env": {
42
+ "GOGGLES_ROOT": ".",
43
+ "GOGGLES_PLANES_PATH": ""
44
+ }
37
45
  }
38
46
  }
39
47
  }
@@ -55,11 +55,11 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
55
55
 
56
56
  ## Goggles
57
57
 
58
- Pushed on `feat/goggles-look`. Not merged. Not published. Grok CLI has not shipped this. Wear the local build. From the worn checkout run `node dist/integrations/hooks/goggles-pipeline.mjs`. Do not run `npx 0xray goggles`. If `dist` is older than `src/integrations/hooks/goggles-pipeline.mjs`, build that tree and wear that `dist`. A subagent wears this same local build.
58
+ The command is `0xray look`. `0xray goggles` is the same organ. A pipe keeps the line card. A terminal shows that card as a pane. Chat seats without hooks use the `goggles` MCP tools `look` and `status_lens`. Same organ.
59
59
 
60
60
  Kind 0 is actuality. It compares the list in the file to the same six names in the code. It speaks only when those lists differ. A match is quiet. It does not read the suit that is running. It is not a plate type.
61
61
 
62
- Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked. Calling stays off.
62
+ Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
63
63
 
64
64
  `digest` and `triage` return the card already built by `assemblePlane`. The fields are from, digest, plate, entry, exit, files, skills, setup, teardown, and worn. Empty stays empty. The rows are ground, routing, house, boot, governance, memory-recall, orchestration, processor, and reporting. Those nine are not Kind 0. `digest` fills the card. Entry and exit are the plate's input and output box titles when that plate has them. Setup and teardown stay empty unless the row names them. The mill path is `scripts/foundry`. `triage` adds the empty fields and whether the row holds. Zoom after the card is the same ladder: ecosystem, part, one flow, one artifact. One file is one artifact. Several files are not narrowed to a guessed file.
65
65
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./features.schema.json",
3
- "version": "4.0.31",
3
+ "version": "4.0.32",
4
4
  "description": "0xRay Framework - Unified Feature Configuration",
5
5
  "suit_temperament": {
6
6
  "profile": "auto",
package/dist/CHANGELOG.md CHANGED
@@ -42,6 +42,14 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
42
42
  - Archive root `integrations/` costume SKILL dump → `docs/archive/integrations-costume/`. Live wear is `src/integrations`.
43
43
  - Archive root `commands/` stale copy → `docs/archive/commands-copy/`. Live wear is `src/opencode/commands`.
44
44
 
45
+ ## [4.0.32] - 2026-10-01
46
+
47
+ Next patch after npm `4.0.31`. Not published from this commit.
48
+
49
+ Organ bolster for goggles-pipeline after #167. `0xray look` (and `goggles`) opens one plane through the organ. Digest and triage return the card pane. Status and health show the organ worn and Kind 0 quiet/match. Bot Plugin MCP `goggles` exposes lean tools `look` and `status_lens` over stdio on the same organ. Kind 0 name-checks the six outers and stays quiet on match; reading stays a dichotomy; digest/triage stay the card. Calling and confer stay off. Companion `@0xray/grok-bot` is unchanged at 0.1.8.
50
+
51
+ ---
52
+
45
53
  ## [4.0.31] - 2026-10-01
46
54
 
47
55
  Next patch after npm `4.0.30`. Not published from this commit.
@@ -10,6 +10,7 @@
10
10
  *
11
11
  * Usage: npx 0xray status
12
12
  */
13
+ import type { GogglesStatus } from "../goggles-status.js";
13
14
  interface StatusReport {
14
15
  opencode: {
15
16
  installed: boolean;
@@ -34,8 +35,9 @@ interface StatusReport {
34
35
  outcomesCount: number;
35
36
  patternsCount: number;
36
37
  };
38
+ goggles: GogglesStatus;
37
39
  }
38
- export declare function getStatusReport(cwd?: string): StatusReport;
40
+ export declare function getStatusReport(cwd?: string, packageRoot?: string): StatusReport;
39
41
  export declare function printStatus(report: StatusReport): void;
40
- export declare function statusCommand(): Promise<void>;
42
+ export declare function statusCommand(packageRoot?: string): Promise<void>;
41
43
  export default statusCommand;
@@ -14,6 +14,7 @@ import { readdirSync, existsSync, readFileSync } from "fs";
14
14
  import { join } from "path";
15
15
  import { getConfigDir } from "../../core/config-paths.js";
16
16
  import { featuresConfigLoader } from "../../core/features-config.js";
17
+ import { formatGogglesStatus, probeGoggles } from "../goggles-status.js";
17
18
  function getSkillsList(cwd) {
18
19
  const skills = [];
19
20
  const configDir = getConfigDir(cwd);
@@ -119,7 +120,7 @@ function getInferenceStatus(cwd) {
119
120
  catch { /* ignore */ }
120
121
  return { active, lastTuning, outcomesCount, patternsCount };
121
122
  }
122
- export function getStatusReport(cwd = process.cwd()) {
123
+ export function getStatusReport(cwd = process.cwd(), packageRoot = cwd) {
123
124
  const opencodeConfigPath = join(cwd, "opencode.json");
124
125
  const cwdSkills = getSkillsList(cwd);
125
126
  const agents = getAgentsList(cwd);
@@ -134,6 +135,7 @@ export function getStatusReport(cwd = process.cwd()) {
134
135
  agents,
135
136
  health,
136
137
  inference,
138
+ goggles: probeGoggles(packageRoot, cwd),
137
139
  };
138
140
  }
139
141
  export function printStatus(report) {
@@ -166,6 +168,7 @@ export function printStatus(report) {
166
168
  console.log(`⚙️ Inference: ${inferenceStatus} (${report.inference.outcomesCount} outcomes, ${report.inference.patternsCount} patterns)`);
167
169
  console.log(`📊 Health: ${healthStatus}`);
168
170
  console.log("");
171
+ process.stdout.write(`${formatGogglesStatus(report.goggles).join("\n")}\n`);
169
172
  if (report.skills.names.length > 0) {
170
173
  console.log("Installed Skills:");
171
174
  const cols = 4;
@@ -191,9 +194,9 @@ export function printStatus(report) {
191
194
  console.log("");
192
195
  }
193
196
  }
194
- export async function statusCommand() {
197
+ export async function statusCommand(packageRoot = process.cwd()) {
195
198
  const cwd = process.cwd();
196
- const report = getStatusReport(cwd);
199
+ const report = getStatusReport(cwd, packageRoot);
197
200
  printStatus(report);
198
201
  }
199
202
  export default statusCommand;
@@ -23,6 +23,10 @@ export const VALIDATE_PACK_PATHS = [
23
23
  "dist/integrations/cursor/hooks/pre-compact.sh",
24
24
  "dist/integrations/cursor/hooks/after-file-edit.sh",
25
25
  "dist/integrations/hooks/plates.cjs",
26
+ "dist/integrations/hooks/goggles-pipeline.mjs",
27
+ "dist/integrations/hooks/goggles-planes.json",
28
+ "dist/integrations/hooks/goggles-mcp.mjs",
29
+ "scripts/mjs/run-goggles-mcp.mjs",
26
30
  "docs-site/docs/plates/memory-recall.md",
27
31
  "docs-site/docs/plates/processor.md",
28
32
  "docs-site/docs/plates/goggles.md",
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Goggles on status and health.
3
+ * Organ present is not Kind 0. Quiet is a match. No phone-home.
4
+ */
5
+ export type GogglesStatus = {
6
+ organPresent: boolean;
7
+ kind0Text: string;
8
+ kind0Quiet: boolean;
9
+ kind0Label: string;
10
+ organLine: string;
11
+ };
12
+ export declare function probeGoggles(packageRoot: string, cwd: string): GogglesStatus;
13
+ export declare function formatGogglesStatus(probe: GogglesStatus): string[];
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Goggles on status and health.
3
+ * Organ present is not Kind 0. Quiet is a match. No phone-home.
4
+ */
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ const WORN_PLANES = ["dichotomy", "syncopate", "synthesis", "digest", "triage", "loop"];
8
+ const ORGAN_PAIRS = [
9
+ ["dist/integrations/hooks/goggles-pipeline.mjs", "dist/integrations/hooks/goggles-planes.json"],
10
+ ["src/integrations/hooks/goggles-pipeline.mjs", "src/integrations/hooks/goggles-planes.json"],
11
+ ];
12
+ function organPair(packageRoot) {
13
+ for (const pair of ORGAN_PAIRS) {
14
+ if (existsSync(join(packageRoot, pair[0])) && existsSync(join(packageRoot, pair[1]))) {
15
+ return pair;
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+ function lensText(cwd) {
21
+ const file = join(cwd, ".xray", "state", "LENS.md");
22
+ if (!existsSync(file))
23
+ return { found: false, text: "" };
24
+ try {
25
+ return { found: true, text: readFileSync(file, "utf8").trim() };
26
+ }
27
+ catch {
28
+ return { found: false, text: "" };
29
+ }
30
+ }
31
+ function mapDrift(packageRoot, planesRel) {
32
+ try {
33
+ const data = JSON.parse(readFileSync(join(packageRoot, planesRel), "utf8"));
34
+ const map = Array.isArray(data.planes) ? data.planes.map((name) => String(name)) : [];
35
+ const same = map.length === WORN_PLANES.length && map.every((name, index) => name === WORN_PLANES[index]);
36
+ return same ? "" : "Actuality. Drift: worn is not the map.";
37
+ }
38
+ catch {
39
+ return "";
40
+ }
41
+ }
42
+ export function probeGoggles(packageRoot, cwd) {
43
+ const pair = organPair(packageRoot);
44
+ const organPresent = pair !== null;
45
+ const lens = lensText(cwd);
46
+ let kind0Text = "";
47
+ let kind0Label = "(no LENS yet)";
48
+ let kind0Quiet = false;
49
+ if (lens.found) {
50
+ kind0Text = lens.text;
51
+ if (!lens.text) {
52
+ kind0Quiet = true;
53
+ kind0Label = "quiet (match)";
54
+ }
55
+ else {
56
+ kind0Label = lens.text;
57
+ }
58
+ }
59
+ else if (pair) {
60
+ const drift = mapDrift(packageRoot, pair[1]);
61
+ if (drift) {
62
+ kind0Text = drift;
63
+ kind0Label = drift;
64
+ }
65
+ }
66
+ return {
67
+ organPresent,
68
+ kind0Text,
69
+ kind0Quiet,
70
+ kind0Label,
71
+ organLine: organPresent ? "Goggles: worn" : "Goggles: not found",
72
+ };
73
+ }
74
+ export function formatGogglesStatus(probe) {
75
+ return [probe.organLine, `Kind 0: ${probe.kind0Label}`];
76
+ }
package/dist/cli/index.js CHANGED
@@ -76,20 +76,42 @@ program
76
76
  process.exit(1);
77
77
  }
78
78
  });
79
- program
80
- .command("goggles")
81
- .argument("[args...]", "plane, then examine, triage, cascade, or teardown")
82
- .description("Glimpse every plane, or one plane's way in. A hit does not open the plane.")
83
- .action((args) => {
79
+ const LOOK_HELP = "Kind 0 is quiet on a match. One outer is a reading. digest and triage return a card.";
80
+ function gogglesScript() {
84
81
  const built = join(packageRoot, "dist", "integrations", "hooks", "goggles-pipeline.mjs");
85
82
  const source = join(packageRoot, "src", "integrations", "hooks", "goggles-pipeline.mjs");
86
- const script = existsSync(built) ? built : source;
83
+ return existsSync(built) ? built : source;
84
+ }
85
+ function runGoggles(args) {
86
+ const script = gogglesScript();
87
+ if (!existsSync(script)) {
88
+ process.stderr.write("Goggles: not found\n");
89
+ process.exit(1);
90
+ }
91
+ if (process.stdout.isTTY) {
92
+ const result = spawnSync(process.execPath, [script, ...(args ?? [])], { stdio: "inherit" });
93
+ process.exit(result.status ?? 1);
94
+ }
87
95
  const result = spawnSync(process.execPath, [script, ...(args ?? [])], { encoding: "utf8" });
88
96
  if (result.stdout)
89
97
  process.stdout.write(result.stdout);
90
98
  if (result.stderr)
91
99
  process.stderr.write(result.stderr);
92
100
  process.exit(result.status ?? 1);
101
+ }
102
+ program
103
+ .command("look")
104
+ .argument("[args...]", "kind 0, one outer, or digest/triage and one card plane")
105
+ .description(LOOK_HELP)
106
+ .action((args) => {
107
+ runGoggles(args);
108
+ });
109
+ program
110
+ .command("goggles")
111
+ .argument("[args...]", "same words as look")
112
+ .description(`Same organ as look. ${LOOK_HELP}`)
113
+ .action((args) => {
114
+ runGoggles(args);
93
115
  });
94
116
  program
95
117
  .command("wear")
@@ -130,7 +152,7 @@ program
130
152
  .description("Show comprehensive xray framework status")
131
153
  .action(async () => {
132
154
  const { statusCommand } = await import("./commands/status.js");
133
- await statusCommand();
155
+ await statusCommand(packageRoot);
134
156
  });
135
157
  program
136
158
  .command("validate")
@@ -267,6 +289,8 @@ program
267
289
  }
268
290
  }
269
291
  console.log("");
292
+ const { probeGoggles, formatGogglesStatus } = await import("./goggles-status.js");
293
+ process.stdout.write(`${formatGogglesStatus(probeGoggles(packageRoot, process.cwd())).join("\n")}\n\n`);
270
294
  if (allHealthy) {
271
295
  console.log("🎉 Framework is healthy and ready to use!");
272
296
  console.log("");
@@ -24,8 +24,11 @@ export interface GrokInstallOptions {
24
24
  export declare function installForGrokCLI(options?: GrokInstallOptions): Promise<void>;
25
25
  /** Grok TUI reads <project>/.grok/config.toml, not the package copy. */
26
26
  export declare function writeProjectRepertoireMcp(projectRoot: string): string | null;
27
+ /** Grok TUI reads <project>/.grok/config.toml. The launcher fills GOGGLES_ROOT from cwd. */
28
+ export declare function writeProjectGogglesMcp(projectRoot: string): string | null;
27
29
  declare const _default: {
28
30
  installForGrokCLI: typeof installForGrokCLI;
29
31
  writeProjectRepertoireMcp: typeof writeProjectRepertoireMcp;
32
+ writeProjectGogglesMcp: typeof writeProjectGogglesMcp;
30
33
  };
31
34
  export default _default;
@@ -27,7 +27,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
27
  const packageRoot = path.resolve(__dirname, '..', '..', '..');
28
28
  const requireCjs = createRequire(import.meta.url);
29
29
  const { resolveConsumerTargetDir, patchGrokHooks } = requireCjs(path.join(packageRoot, 'scripts/node/install-bridges.cjs'));
30
- const { XRAY_MCP_SERVERS, resolveRepertoireMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
30
+ const { XRAY_MCP_SERVERS, resolveRepertoireMcp, resolveGogglesMcp } = requireCjs(path.join(packageRoot, 'scripts/node/bridge-mcp-wiring.cjs'));
31
31
  function registerGrokMcpServers(targetDir) {
32
32
  try {
33
33
  execSync('which grok', { stdio: 'ignore' });
@@ -113,6 +113,7 @@ export async function installForGrokCLI(options = {}) {
113
113
  }
114
114
  }
115
115
  writeProjectRepertoireMcp(targetDir);
116
+ writeProjectGogglesMcp(targetDir);
116
117
  mintAfterWear(targetDir);
117
118
  const primary = dests[0];
118
119
  try {
@@ -177,6 +178,7 @@ function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
177
178
  }
178
179
  }
179
180
  const repertoireMcp = resolveRepertoireMcp(targetDir);
181
+ const gogglesMcp = resolveGogglesMcp(targetDir);
180
182
  if (mcp.mcpServers) {
181
183
  if (repertoireMcp) {
182
184
  mcp.mcpServers.repertoire = {
@@ -187,6 +189,16 @@ function pinGrokPluginToInstalledDist(pluginDir, xrayRoot, targetDir) {
187
189
  else {
188
190
  delete mcp.mcpServers.repertoire;
189
191
  }
192
+ if (gogglesMcp) {
193
+ mcp.mcpServers.goggles = {
194
+ command: 'node',
195
+ args: [gogglesMcp],
196
+ env: { GOGGLES_ROOT: targetDir, GOGGLES_PLANES_PATH: '' },
197
+ };
198
+ }
199
+ else {
200
+ delete mcp.mcpServers.goggles;
201
+ }
190
202
  }
191
203
  fs.writeFileSync(mcpPath, `${JSON.stringify(mcp, null, 2)}\n`);
192
204
  }
@@ -217,7 +229,35 @@ enabled = true
217
229
  }
218
230
  return tomlPath;
219
231
  }
232
+ /** Grok TUI reads <project>/.grok/config.toml. The launcher fills GOGGLES_ROOT from cwd. */
233
+ export function writeProjectGogglesMcp(projectRoot) {
234
+ const gogglesMcp = resolveGogglesMcp(projectRoot);
235
+ if (!gogglesMcp)
236
+ return null;
237
+ const grokDir = path.join(projectRoot, '.grok');
238
+ fs.mkdirSync(grokDir, { recursive: true });
239
+ const tomlPath = path.join(grokDir, 'config.toml');
240
+ const block = `[mcp_servers.goggles]
241
+ command = "node"
242
+ args = [${JSON.stringify(gogglesMcp)}]
243
+ enabled = true
244
+ `;
245
+ let existing = '';
246
+ if (fs.existsSync(tomlPath)) {
247
+ existing = fs.readFileSync(tomlPath, 'utf8');
248
+ }
249
+ if (/\[mcp_servers\.goggles\]/.test(existing)) {
250
+ existing = existing.replace(/\[mcp_servers\.goggles\][\s\S]*?(?=\n\[|$)/, block.trim());
251
+ fs.writeFileSync(tomlPath, existing.endsWith('\n') ? existing : `${existing}\n`);
252
+ }
253
+ else {
254
+ const prefix = existing.trim() ? `${existing.trim()}\n\n` : '';
255
+ fs.writeFileSync(tomlPath, `${prefix}${block}`);
256
+ }
257
+ return tomlPath;
258
+ }
220
259
  export default {
221
260
  installForGrokCLI,
222
261
  writeProjectRepertoireMcp,
262
+ writeProjectGogglesMcp,
223
263
  };
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Goggles MCP. Stdio. Server name `goggles`.
3
+ * Tools: look, status_lens. Same organ as the CLI.
4
+ */
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { basename, join, resolve } from 'node:path';
7
+ import { pathToFileURL, fileURLToPath } from 'node:url';
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
11
+ import * as bundled from './goggles-pipeline.mjs';
12
+
13
+ const HERE = fileURLToPath(new URL('.', import.meta.url));
14
+ const SERVER_NAME = 'goggles';
15
+ const DRAWING = /docs-site\/docs\/plates\/[a-z0-9-]+\.md/i;
16
+ const CARD_PLANES = [
17
+ 'ground',
18
+ 'routing',
19
+ 'house',
20
+ 'boot',
21
+ 'governance',
22
+ 'memory-recall',
23
+ 'orchestration',
24
+ 'processor',
25
+ 'reporting',
26
+ ];
27
+ const SCOPES = ['ecosystem', 'part', 'one flow', 'one artifact'];
28
+
29
+ const LOOK_SCHEMA = {
30
+ type: 'object',
31
+ additionalProperties: false,
32
+ properties: {
33
+ kind: {
34
+ type: 'string',
35
+ description: 'Optional. Use "0" or "actuality" for the lens. Omit when naming an outer.',
36
+ enum: ['0', 'actuality'],
37
+ },
38
+ outer: {
39
+ type: 'string',
40
+ description: 'One outer name. digest and triage return a card. The others return a reading.',
41
+ enum: ['dichotomy', 'syncopate', 'synthesis', 'digest', 'triage', 'loop'],
42
+ },
43
+ scope: {
44
+ type: 'string',
45
+ description: 'Zoom only. Not a kind. One level.',
46
+ enum: SCOPES,
47
+ },
48
+ plane: {
49
+ type: 'string',
50
+ description: 'Card plane for digest or triage only. Not the lens.',
51
+ enum: CARD_PLANES,
52
+ },
53
+ },
54
+ };
55
+
56
+ export const TOOLS = [
57
+ {
58
+ name: 'look',
59
+ description: 'Kind 0 is quiet on a match. It checks the six outer names. One outer is a reading. digest and triage return a card.',
60
+ inputSchema: LOOK_SCHEMA,
61
+ },
62
+ {
63
+ name: 'status_lens',
64
+ description: 'Whether the organ is worn, and the last Kind 0. Quiet is a match. A missing organ is not drift.',
65
+ inputSchema: { type: 'object', additionalProperties: false, properties: {} },
66
+ },
67
+ ];
68
+
69
+ function serverVersion() {
70
+ try {
71
+ const pkg = JSON.parse(readFileSync(join(HERE, '../../../package.json'), 'utf8'));
72
+ return typeof pkg.version === 'string' && pkg.version ? pkg.version : 'local';
73
+ } catch {
74
+ return 'local';
75
+ }
76
+ }
77
+
78
+ function deny(code, reason) {
79
+ return {
80
+ ok: false,
81
+ quiet: false,
82
+ mode: 'deny',
83
+ content: '',
84
+ error: { gate: 'goggles', decision: 'deny', code, reason },
85
+ };
86
+ }
87
+
88
+ function quiet(mode) {
89
+ return { ok: true, quiet: true, mode, content: '' };
90
+ }
91
+
92
+ async function organApi() {
93
+ const override = typeof process.env.GOGGLES_ORGAN_PATH === 'string'
94
+ ? process.env.GOGGLES_ORGAN_PATH.trim()
95
+ : '';
96
+ if (!override) return bundled;
97
+ if (!existsSync(override)) return null;
98
+ return import(pathToFileURL(override).href);
99
+ }
100
+
101
+ function projectRoot(root) {
102
+ if (typeof root === 'string' && root.trim()) return root;
103
+ const env = process.env.GOGGLES_ROOT;
104
+ if (typeof env === 'string' && env.trim() && env.trim() !== '.') return env.trim();
105
+ return process.cwd();
106
+ }
107
+
108
+ function heldReading(root) {
109
+ try {
110
+ const data = JSON.parse(readFileSync(join(root, '.xray', 'state', 'goggles-reading.json'), 'utf8'));
111
+ if (!data || typeof data !== 'object') return null;
112
+ if (typeof data.drift === 'string' && data.drift) return { drift: data.drift };
113
+ if (typeof data.plane !== 'string' || !data.plane) return null;
114
+ return { plane: data.plane, scope: typeof data.scope === 'string' ? data.scope : '' };
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+
120
+ function denyCode(reason) {
121
+ if (/wider|crosses parts|not one flow/i.test(reason)) return 'scope_wider';
122
+ if (/Name one plane/i.test(reason)) return 'name_one';
123
+ return 'leave_plane';
124
+ }
125
+
126
+ function drawingPaths(args) {
127
+ const hits = [];
128
+ for (const value of Object.values(args)) {
129
+ if (typeof value === 'string' && DRAWING.test(value)) hits.push(value);
130
+ }
131
+ return hits;
132
+ }
133
+
134
+ function argvFrom(args) {
135
+ const words = [];
136
+ if (args.kind === '0') words.push('kind', '0');
137
+ else if (args.kind === 'actuality') words.push('actuality');
138
+ else if (args.kind === '1' || args.kind === '2') words.push('kind', String(args.kind));
139
+ else if (typeof args.kind === 'string') words.push(args.kind);
140
+ if (args.outer === 'outer loop') words.push('outer', 'loop');
141
+ else if (typeof args.outer === 'string') words.push(args.outer);
142
+ if (typeof args.plane === 'string' && !DRAWING.test(args.plane)) words.push(args.plane);
143
+ if (args.scope === 'one flow') words.push('one', 'flow');
144
+ else if (args.scope === 'one artifact') words.push('one', 'artifact');
145
+ else if (typeof args.scope === 'string') words.push(args.scope);
146
+ return words;
147
+ }
148
+
149
+ function pureKind0(args) {
150
+ return (args.kind === '0' || args.kind === 'actuality')
151
+ && args.outer == null
152
+ && args.plane == null
153
+ && args.scope == null;
154
+ }
155
+
156
+ function checkArgs(args) {
157
+ if (!args || typeof args !== 'object' || Array.isArray(args)) {
158
+ return deny('invalid_args', 'look takes an object.');
159
+ }
160
+ const allowed = new Set(['kind', 'outer', 'scope', 'plane']);
161
+ for (const key of Object.keys(args)) {
162
+ if (!allowed.has(key)) return deny('invalid_args', 'Unknown argument.');
163
+ }
164
+ for (const key of ['kind', 'outer', 'scope', 'plane']) {
165
+ if (args[key] != null && typeof args[key] !== 'string') {
166
+ return deny('invalid_args', `${key} must be a string.`);
167
+ }
168
+ }
169
+ if (args.kind != null && !['0', 'actuality', '1', '2'].includes(args.kind)) {
170
+ return deny('invalid_args', 'kind is 0 or actuality.');
171
+ }
172
+ if (args.outer != null && !LOOK_SCHEMA.properties.outer.enum.includes(args.outer)) {
173
+ const setName = args.outer === 'outer' || args.outer === 'outer-loop' || args.outer === 'outer loop';
174
+ if (!setName) return deny('invalid_args', 'Name one outer.');
175
+ }
176
+ if (args.scope != null && !SCOPES.includes(args.scope)) {
177
+ return deny('invalid_args', 'scope is one zoom level.');
178
+ }
179
+ if (args.plane != null && !CARD_PLANES.includes(args.plane) && !DRAWING.test(args.plane)) {
180
+ return deny('invalid_args', 'plane is a card plane.');
181
+ }
182
+ return null;
183
+ }
184
+
185
+ function publicCard(view) {
186
+ const notes = [];
187
+ if (view.flavor === 'triage') {
188
+ if (Array.isArray(view.empty) && view.empty.length) notes.push(`Empty: ${view.empty.join(', ')}`);
189
+ if (view.exam) notes.push(view.exam);
190
+ }
191
+ const skills = view.skills ? basename(String(view.skills)) : '';
192
+ if (view.narrow) {
193
+ return {
194
+ plane: view.plane,
195
+ flavor: view.flavor,
196
+ digest: view.digest || '',
197
+ files: Array.isArray(view.files) ? view.files : [],
198
+ notes,
199
+ };
200
+ }
201
+ return {
202
+ plane: view.plane,
203
+ flavor: view.flavor,
204
+ from: view.from || '',
205
+ digest: view.digest || '',
206
+ plate: view.plate || '',
207
+ entry: view.entry || '',
208
+ exit: view.exit || '',
209
+ files: Array.isArray(view.files) ? view.files : [],
210
+ skills,
211
+ setup: view.setup || '',
212
+ teardown: view.teardown || '',
213
+ worn: view.worn || '',
214
+ notes,
215
+ };
216
+ }
217
+
218
+ export async function answerLook(raw, root) {
219
+ if (raw != null && (typeof raw !== 'object' || Array.isArray(raw))) {
220
+ return deny('invalid_args', 'look takes an object.');
221
+ }
222
+ const args = raw && typeof raw === 'object' ? raw : {};
223
+ const bad = checkArgs(args);
224
+ if (bad) return bad;
225
+ const api = await organApi();
226
+ if (!api) return deny('invalid_args', 'Goggles organ not found.');
227
+ const here = projectRoot(root);
228
+ const drawings = drawingPaths(args);
229
+ if (drawings.length) {
230
+ const stop = api.organStop(heldReading(here), { text: drawings.join('\n'), paths: drawings, tool: 'look' });
231
+ if (stop && stop.reason) return deny(denyCode(stop.reason), stop.reason);
232
+ return quiet('reading');
233
+ }
234
+ if (pureKind0(args)) {
235
+ api.maintainLens(here);
236
+ const line = api.actualityLine();
237
+ if (!line) return quiet('kind0');
238
+ return { ok: true, quiet: false, mode: 'kind0', content: line };
239
+ }
240
+ const argv = argvFrom(args);
241
+ const text = api.look(argv).text;
242
+ if (text === 'Name one plane.') return deny('name_one', 'Name one plane.');
243
+ const cards = api.lookCards(argv);
244
+ if (Array.isArray(cards) && cards.length) {
245
+ const shown = cards.map(publicCard);
246
+ return {
247
+ ok: true,
248
+ quiet: false,
249
+ mode: 'card',
250
+ content: shown.length === 1 ? shown[0] : shown,
251
+ outer: args.outer,
252
+ scope: args.scope || null,
253
+ };
254
+ }
255
+ if (!text) {
256
+ if (args.kind === '0' || args.kind === 'actuality') return quiet('kind0');
257
+ if (args.outer === 'digest' || args.outer === 'triage') return quiet('card');
258
+ return quiet('reading');
259
+ }
260
+ if (text.startsWith('Actuality. Drift:')) {
261
+ return { ok: true, quiet: false, mode: 'kind0', content: text };
262
+ }
263
+ return {
264
+ ok: true,
265
+ quiet: false,
266
+ mode: 'reading',
267
+ content: text,
268
+ outer: args.outer ?? null,
269
+ scope: args.scope || null,
270
+ };
271
+ }
272
+
273
+ function lensBody(root) {
274
+ const file = join(root, '.xray', 'state', 'LENS.md');
275
+ if (!existsSync(file)) return { found: false, text: '' };
276
+ try {
277
+ return { found: true, text: readFileSync(file, 'utf8').trim() };
278
+ } catch {
279
+ return { found: false, text: '' };
280
+ }
281
+ }
282
+
283
+ export async function answerStatus(root) {
284
+ const api = await organApi();
285
+ const override = typeof process.env.GOGGLES_ORGAN_PATH === 'string'
286
+ ? process.env.GOGGLES_ORGAN_PATH.trim()
287
+ : '';
288
+ const organPath = override || fileURLToPath(new URL('./goggles-pipeline.mjs', import.meta.url));
289
+ const planesPath = api ? api.planesFile() : '';
290
+ const organOk = existsSync(organPath);
291
+ const planesOk = Boolean(planesPath) && existsSync(planesPath);
292
+ const worn = Boolean(api) && organOk && planesOk;
293
+ const here = projectRoot(root);
294
+ const lens = lensBody(here);
295
+ let kind0 = 'none';
296
+ let kind0Text = '';
297
+ if (lens.found && !lens.text) kind0 = 'quiet';
298
+ else if (lens.found) {
299
+ kind0 = 'drift';
300
+ kind0Text = lens.text;
301
+ } else if (worn) {
302
+ const line = api.actualityLine();
303
+ if (line) {
304
+ kind0 = 'drift';
305
+ kind0Text = line;
306
+ }
307
+ }
308
+ return {
309
+ ok: true,
310
+ organ: worn ? 'worn' : 'not_found',
311
+ organPath: organOk ? organPath : '',
312
+ planesPath: planesOk ? planesPath : '',
313
+ kind0,
314
+ kind0Text,
315
+ lensPath: '.xray/state/LENS.md',
316
+ };
317
+ }
318
+
319
+ export async function dispatchTool(name, args, root) {
320
+ const bare = String(name || '').replace(/^goggles__/, '');
321
+ if (bare === 'status_lens') {
322
+ const input = args && typeof args === 'object' ? args : {};
323
+ if (Object.keys(input).length) {
324
+ return { payload: deny('invalid_args', 'status_lens takes no arguments.'), isError: true };
325
+ }
326
+ return { payload: await answerStatus(root), isError: false };
327
+ }
328
+ if (bare === 'look') {
329
+ const payload = await answerLook(args, root);
330
+ return { payload, isError: payload.mode === 'deny' };
331
+ }
332
+ return { payload: deny('invalid_args', 'Unknown tool.'), isError: true };
333
+ }
334
+
335
+ export function createGogglesServer() {
336
+ const server = new Server(
337
+ { name: SERVER_NAME, version: serverVersion() },
338
+ { capabilities: { tools: {} } },
339
+ );
340
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
341
+ tools: TOOLS.map((tool) => ({
342
+ name: tool.name,
343
+ description: tool.description,
344
+ inputSchema: tool.inputSchema,
345
+ })),
346
+ }));
347
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
348
+ const args = request.params.arguments ?? {};
349
+ const { payload, isError } = await dispatchTool(request.params.name, args, projectRoot());
350
+ return {
351
+ content: [{ type: 'text', text: JSON.stringify(payload) }],
352
+ isError,
353
+ };
354
+ });
355
+ return server;
356
+ }
357
+
358
+ async function main() {
359
+ const server = createGogglesServer();
360
+ const transport = new StdioServerTransport();
361
+ await server.connect(transport);
362
+ }
363
+
364
+ const thisFile = fileURLToPath(import.meta.url);
365
+ const invoked = process.argv[1] ? resolve(process.argv[1]) : '';
366
+ if (invoked && resolve(thisFile) === invoked) {
367
+ main().catch((err) => {
368
+ process.stderr.write(`goggles MCP failed: ${err instanceof Error ? err.message : String(err)}\n`);
369
+ process.exit(1);
370
+ });
371
+ }
@@ -14,9 +14,16 @@ const CARD_PLANES = ['ground', 'routing', 'house', 'boot', 'governance', 'memory
14
14
  const CARD_FLAVORS = ['digest', 'triage'];
15
15
  const SCOPE_ZOOM = ['ecosystem', 'part', 'one flow', 'one artifact'];
16
16
 
17
+ /** Empty GOGGLES_PLANES_PATH keeps the file beside the organ. */
18
+ export function planesFile() {
19
+ const override = process.env.GOGGLES_PLANES_PATH;
20
+ if (typeof override === 'string' && override.trim()) return override.trim();
21
+ return join(HERE, 'goggles-planes.json');
22
+ }
23
+
17
24
  function drawnPlanes() {
18
25
  try {
19
- const data = JSON.parse(readFileSync(join(HERE, 'goggles-planes.json'), 'utf8'));
26
+ const data = JSON.parse(readFileSync(planesFile(), 'utf8'));
20
27
  return Array.isArray(data.planes) ? data.planes.map((name) => String(name)) : [];
21
28
  } catch {
22
29
  return [];
@@ -169,7 +176,7 @@ function namesTheSet(lower) {
169
176
  return planes.length === 1 && planes[0] === 'loop' && findScopes(lower).length === 0;
170
177
  }
171
178
 
172
- export function readGoggles(argv) {
179
+ function gogglesAnswer(argv) {
173
180
  const words = [];
174
181
  for (const raw of argv || []) {
175
182
  const arg = String(raw || '').trim();
@@ -177,102 +184,157 @@ export function readGoggles(argv) {
177
184
  words.push(arg);
178
185
  }
179
186
  const lower = words.map((word) => word.toLowerCase());
180
- if (lower.includes('calling')) return ok('');
181
- if (kindPastOne(words)) return ok('');
182
- if (words.length === 0) return ok('');
183
- if (namesTheSet(lower)) return ok('Name one plane.');
187
+ const none = (text) => ({ text, cards: null });
188
+ if (lower.includes('calling')) return none('');
189
+ if (kindPastOne(words)) return none('');
190
+ if (words.length === 0) return none('');
191
+ if (namesTheSet(lower)) return none('Name one plane.');
184
192
  if (isKindZero(lower)) {
185
- if (findScopes(words).length) return ok('');
186
- if (acceptedPlanes().some((name) => lower.includes(name))) return ok('');
187
- return ok(actualityLine());
193
+ if (findScopes(words).length) return none('');
194
+ if (acceptedPlanes().some((name) => lower.includes(name))) return none('');
195
+ return none(actualityLine());
188
196
  }
189
197
  const flavors = CARD_FLAVORS.filter((name) => lower.includes(name));
190
- if (flavors.length > 1) return ok('');
198
+ if (flavors.length > 1) return none('');
191
199
  if (flavors.length === 1) {
192
200
  const others = acceptedPlanes().filter((name) => name !== flavors[0] && lower.includes(name));
193
- if (others.length) return ok('');
194
- return ok(cardLook(flavors[0], lower));
201
+ if (others.length) return none('');
202
+ const cards = collectCards(flavors[0], lower);
203
+ if (!cards) return none('');
204
+ return { text: cards.map((card) => formatCardText(card)).join('\n\n'), cards };
195
205
  }
196
206
  const planes = acceptedPlanes().filter((name) => lower.includes(name));
197
- if (planes.length !== 1) return ok('');
207
+ if (planes.length !== 1) return none('');
198
208
  const scopes = findScopes(words.filter((word) => word.toLowerCase() !== planes[0]));
199
- if (scopes.length > 1) return ok('');
200
- return ok(readingLine(planes[0], scopes[0] || ''));
209
+ if (scopes.length > 1) return none('');
210
+ return none(readingLine(planes[0], scopes[0] || ''));
211
+ }
212
+
213
+ export function readGoggles(argv) {
214
+ return ok(gogglesAnswer(argv).text);
215
+ }
216
+
217
+ export function lookCards(argv) {
218
+ return gogglesAnswer(argv).cards;
201
219
  }
202
220
 
203
221
  function cardPlanesNamed(lower) {
204
222
  return CARD_PLANES.filter((name) => lower.includes(name));
205
223
  }
206
224
 
207
- function fileList(plane) {
208
- const files = Array.isArray(plane.files) ? plane.files : [];
225
+ function listedFiles(plane, scope) {
226
+ const files = Array.isArray(plane.files) ? plane.files.map(String) : [];
227
+ if (scope === 'one artifact') return files.length === 1 ? [files[0]] : null;
209
228
  const unpathed = Array.isArray(plane.unpathed) ? plane.unpathed : [];
210
- const parts = [...files];
211
- for (const name of unpathed) {
212
- parts.push(name === 'mill' ? 'mill has no path' : name);
229
+ const parts = files.slice();
230
+ for (const name of unpathed) parts.push(name === 'mill' ? 'mill has no path' : String(name));
231
+ return parts;
232
+ }
233
+
234
+ function cardView(plane, scope, flavor, root) {
235
+ const listed = listedFiles(plane, scope);
236
+ if (!listed) return null;
237
+ const view = {
238
+ plane: plane.id,
239
+ flavor,
240
+ scope: scope || '',
241
+ from: plane.id === 'ground' ? '' : 'ground',
242
+ digest: plane.digest || '',
243
+ plate: plane.plate || '',
244
+ entry: plane.entry || '',
245
+ exit: plane.exit || '',
246
+ files: listed,
247
+ skills: plane.skills || '',
248
+ setup: plane.setup || '',
249
+ teardown: plane.teardown || '',
250
+ worn: plane.worn || '',
251
+ narrow: scope === 'one flow' || scope === 'one artifact',
252
+ };
253
+ if (flavor === 'triage') {
254
+ const filled = new Set(filledOf(plane));
255
+ view.empty = FIELD_ORDER.filter((name) => !filled.has(name));
256
+ view.exam = examinePlane(plane, root).text;
213
257
  }
214
- return parts.join(', ');
258
+ return view;
215
259
  }
216
260
 
217
- function cardRows(plane, scope) {
218
- const files = Array.isArray(plane.files) ? plane.files : [];
219
- if (scope === 'one artifact' && files.length !== 1) return null;
261
+ function formatCardText(view) {
220
262
  const rows = [
221
- ['Plane', plane.id],
222
- ['From', plane.id === 'ground' ? '' : 'ground'],
223
- ['Digest', plane.digest || ''],
224
- ['Plate', plane.plate || ''],
225
- ['Entry', plane.entry || ''],
226
- ['Exit', plane.exit || ''],
227
- ['Files', scope === 'one artifact' ? files[0] : fileList(plane)],
228
- ['Skills', plane.skills || ''],
229
- ['Setup', plane.setup || ''],
230
- ['Teardown', plane.teardown || ''],
231
- ['Worn', plane.worn || ''],
263
+ ['Plane', view.plane],
264
+ ['From', view.from],
265
+ ['Digest', view.digest],
266
+ ['Plate', view.plate],
267
+ ['Entry', view.entry],
268
+ ['Exit', view.exit],
269
+ ['Files', view.files.join(', ')],
270
+ ['Skills', view.skills],
271
+ ['Setup', view.setup],
272
+ ['Teardown', view.teardown],
273
+ ['Worn', view.worn],
232
274
  ];
233
- if (scope === 'one flow' || scope === 'one artifact') {
234
- return rows.filter(([key]) => key === 'Plane' || key === 'Digest' || key === 'Files');
275
+ const shown = view.narrow
276
+ ? rows.filter(([key]) => key === 'Plane' || key === 'Digest' || key === 'Files')
277
+ : rows;
278
+ let text = shown.map(([key, value]) => `${key}: ${value}`.trimEnd()).join('\n');
279
+ if (view.flavor === 'triage') {
280
+ const lines = [];
281
+ if (view.empty.length) lines.push(`Empty: ${view.empty.join(', ')}`);
282
+ lines.push(view.exam);
283
+ text = `${text}\n${lines.join('\n')}`;
235
284
  }
236
- return rows;
237
- }
238
-
239
- function triageNote(plane, root) {
240
- const filled = new Set(filledOf(plane));
241
- const empty = FIELD_ORDER.filter((name) => !filled.has(name));
242
- const exam = examinePlane(plane, root);
243
- const lines = [];
244
- if (empty.length) lines.push(`Empty: ${empty.join(', ')}`);
245
- lines.push(exam.text);
246
- return lines.join('\n');
285
+ return text;
247
286
  }
248
287
 
249
- function formatPlaneCard(plane, scope, flavor, root) {
250
- const rows = cardRows(plane, scope);
251
- if (!rows) return null;
252
- let text = rows.map(([key, value]) => `${key}: ${value}`.trimEnd()).join('\n');
253
- if (flavor === 'triage') text = `${text}\n${triageNote(plane, root)}`;
254
- return text;
288
+ export function formatCardPane(view) {
289
+ const rows = [['Plane', view.plane]];
290
+ if (!view.narrow) rows.push(['From', view.from]);
291
+ rows.push(['Digest', view.digest]);
292
+ if (!view.narrow) {
293
+ rows.push(
294
+ ['Plate', view.plate],
295
+ ['Entry', view.entry],
296
+ ['Exit', view.exit],
297
+ ['Files', view.files.join(', ')],
298
+ ['Skills', view.skills],
299
+ ['Setup', view.setup],
300
+ ['Teardown', view.teardown],
301
+ ['Worn', view.worn],
302
+ );
303
+ } else {
304
+ rows.push(['Files', view.files.join(', ')]);
305
+ }
306
+ if (view.flavor === 'triage') {
307
+ if (Array.isArray(view.empty) && view.empty.length) rows.push(['Empty', view.empty.join(', ')]);
308
+ if (view.exam) rows.push(['', view.exam]);
309
+ }
310
+ const body = rows.map(([label, value]) => (label ? `${label.padEnd(8)} ${value}` : value).trimEnd());
311
+ const title = `${view.flavor} · ${view.plane}`;
312
+ const width = Math.max(title.length + 2, ...body.map((line) => line.length));
313
+ const top = `┌ ${title} ${'─'.repeat(Math.max(0, width - title.length - 1))}┐`;
314
+ const mid = body.map((line) => `│ ${line.padEnd(width)} │`);
315
+ const bot = `└${'─'.repeat(width + 2)}┘`;
316
+ return [top, ...mid, bot].join('\n');
255
317
  }
256
318
 
257
- function cardLook(flavor, lower) {
319
+ function collectCards(flavor, lower) {
258
320
  const named = cardPlanesNamed(lower);
259
321
  const scopes = findScopes(lower.filter((word) => word !== flavor && !named.includes(word)));
260
- if (named.length > 1 || scopes.length > 1) return '';
322
+ if (named.length > 1 || scopes.length > 1) return null;
261
323
  const scope = scopes[0] || '';
262
- if (!named.length && (scope === 'part' || scope === 'one flow' || scope === 'one artifact')) return '';
324
+ if (!named.length && (scope === 'part' || scope === 'one flow' || scope === 'one artifact')) return null;
263
325
  const platesDir = findPlatesDir(HERE);
264
- if (!platesDir) return '';
326
+ if (!platesDir) return null;
265
327
  const ids = named.length ? named : CARD_PLANES;
266
328
  const root = repoRootFrom(platesDir);
267
- const blocks = [];
329
+ const cards = [];
268
330
  for (const id of ids) {
269
331
  const plane = assemblePlane(id, platesDir);
270
332
  if (!plane) continue;
271
- const text = formatPlaneCard(plane, scope, flavor, root);
272
- if (text === null) return '';
273
- blocks.push(text);
333
+ const view = cardView(plane, scope, flavor, root);
334
+ if (!view) return null;
335
+ cards.push(view);
274
336
  }
275
- return blocks.join('\n\n');
337
+ return cards;
276
338
  }
277
339
 
278
340
  export function findPlatesDir(start) {
@@ -347,7 +409,7 @@ function takeOf(body) {
347
409
 
348
410
 
349
411
  function overlayTable() {
350
- const file = join(HERE, 'goggles-planes.json');
412
+ const file = planesFile();
351
413
  if (!existsSync(file)) return {};
352
414
  return JSON.parse(readFileSync(file, 'utf8'));
353
415
  }
@@ -562,7 +624,9 @@ export function maintainLens(root) {
562
624
 
563
625
  const isMain = Boolean(process.argv[1]) && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
564
626
  if (isMain) {
565
- const result = readGoggles(process.argv.slice(2));
566
- process.stdout.write(`${result.text}\n`);
567
- process.exit(result.ok ? 0 : 1);
627
+ const answered = gogglesAnswer(process.argv.slice(2));
628
+ const pane = process.stdout.isTTY && Array.isArray(answered.cards) && answered.cards.length > 0;
629
+ const text = pane ? answered.cards.map((card) => formatCardPane(card)).join('\n\n') : answered.text;
630
+ process.stdout.write(`${text}\n`);
631
+ process.exit(0);
568
632
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.31",
3
+ "version": "4.0.32",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -55,11 +55,11 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
55
55
 
56
56
  ## Goggles
57
57
 
58
- Pushed on `feat/goggles-look`. Not merged. Not published. Grok CLI has not shipped this. Wear the local build. From the worn checkout run `node dist/integrations/hooks/goggles-pipeline.mjs`. Do not run `npx 0xray goggles`. If `dist` is older than `src/integrations/hooks/goggles-pipeline.mjs`, build that tree and wear that `dist`. A subagent wears this same local build.
58
+ The command is `0xray look`. `0xray goggles` is the same organ. A pipe keeps the line card. A terminal shows that card as a pane. Chat seats without hooks use the `goggles` MCP tools `look` and `status_lens`. Same organ.
59
59
 
60
60
  Kind 0 is actuality. It compares the list in the file to the same six names in the code. It speaks only when those lists differ. A match is quiet. It does not read the suit that is running. It is not a plate type.
61
61
 
62
- Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked. Calling stays off.
62
+ Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
63
63
 
64
64
  `digest` and `triage` return the card already built by `assemblePlane`. The fields are from, digest, plate, entry, exit, files, skills, setup, teardown, and worn. Empty stays empty. The rows are ground, routing, house, boot, governance, memory-recall, orchestration, processor, and reporting. Those nine are not Kind 0. `digest` fills the card. Entry and exit are the plate's input and output box titles when that plate has them. Setup and teardown stay empty unless the row names them. The mill path is `scripts/foundry`. `triage` adds the empty fields and whether the row holds. Zoom after the card is the same ladder: ecosystem, part, one flow, one artifact. One file is one artifact. Several files are not narrowed to a guessed file.
65
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.31",
3
+ "version": "4.0.32",
4
4
  "description": "Multi-agent orchestration and Codex governance for OpenCode, Hermes, Grok Build, and OpenClaw",
5
5
  "readme": "README.md",
6
6
  "license": "MIT",
@@ -170,6 +170,7 @@
170
170
  "scripts/mjs/verify-temperament-live.mjs",
171
171
  "scripts/mjs/verify-four-floor-compact.mjs",
172
172
  "scripts/mjs/run-repertoire-mcp.mjs",
173
+ "scripts/mjs/run-goggles-mcp.mjs",
173
174
  "scripts/mjs/verify-delegation-gate-core.mjs",
174
175
  "scripts/mjs/delegation-gate-fixture.mjs",
175
176
  "src/opencode/",
@@ -29,6 +29,10 @@ export const REQUIRED_PACK_PATHS = [
29
29
  "dist/integrations/cursor/hooks/pre-compact.sh",
30
30
  "dist/integrations/cursor/hooks/after-file-edit.sh",
31
31
  "dist/integrations/hooks/plates.cjs",
32
+ "dist/integrations/hooks/goggles-pipeline.mjs",
33
+ "dist/integrations/hooks/goggles-planes.json",
34
+ "dist/integrations/hooks/goggles-mcp.mjs",
35
+ "scripts/mjs/run-goggles-mcp.mjs",
32
36
  "docs-site/docs/plates/memory-recall.md",
33
37
  "docs-site/docs/plates/processor.md",
34
38
  "docs-site/docs/plates/goggles.md",
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CWD-proof Goggles MCP launcher.
4
+ * Resolves the organ server from this file, not from the caller's cwd.
5
+ */
6
+ import { spawn } from 'node:child_process';
7
+ import { existsSync } from 'node:fs';
8
+ import { dirname, join, resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const xrayRoot = join(dirname(fileURLToPath(import.meta.url)), '../..');
12
+ const candidates = [
13
+ join(xrayRoot, 'dist', 'integrations', 'hooks', 'goggles-mcp.mjs'),
14
+ join(xrayRoot, 'src', 'integrations', 'hooks', 'goggles-mcp.mjs'),
15
+ ];
16
+ const server = candidates.find((file) => existsSync(file));
17
+ if (!server) {
18
+ process.stderr.write('goggles MCP missing\n');
19
+ process.exit(1);
20
+ }
21
+
22
+ const env = { ...process.env };
23
+ if (!env.GOGGLES_ROOT || env.GOGGLES_ROOT === '.') {
24
+ env.GOGGLES_ROOT = resolve(process.cwd());
25
+ }
26
+
27
+ const child = spawn(process.execPath, [server], {
28
+ stdio: 'inherit',
29
+ env,
30
+ });
31
+ child.on('error', (err) => {
32
+ process.stderr.write(`goggles MCP spawn failed: ${err.message}\n`);
33
+ process.exit(1);
34
+ });
35
+ child.on('exit', (code, signal) => {
36
+ if (signal) {
37
+ process.kill(process.pid, signal);
38
+ return;
39
+ }
40
+ process.exit(code ?? 1);
41
+ });
@@ -139,31 +139,70 @@ function enableMemoryRoutingIfResolves(features, targetDir) {
139
139
  };
140
140
  }
141
141
 
142
+ function resolveGogglesMcp(targetDir) {
143
+ const candidates = [
144
+ path.join(targetDir, "node_modules", "0xray", "scripts", "mjs", "run-goggles-mcp.mjs"),
145
+ path.join(targetDir, "scripts", "mjs", "run-goggles-mcp.mjs"),
146
+ ];
147
+ return candidates.find((file) => fs.existsSync(file)) || null;
148
+ }
149
+
150
+ function gogglesScopedLaunch(targetDir, launcher) {
151
+ return wrapScopedLaunch(
152
+ { command: "node", args: [launcher] },
153
+ mergeScopedEnv([
154
+ baseScopedEnv(targetDir),
155
+ { GOGGLES_ROOT: targetDir, GOGGLES_PLANES_PATH: "" },
156
+ ]),
157
+ );
158
+ }
159
+
142
160
  function detectConsumerExtraMcpServers(targetDir) {
143
161
  const extras = { hermes: {}, opencode: {}, openclaw: {} };
144
162
  try {
145
163
  const repertoireMcp = resolveRepertoireMcp(targetDir);
146
- if (!repertoireMcp) return extras;
147
- const launch = wrapScopedLaunch(
148
- { command: "node", args: [repertoireMcp] },
149
- baseScopedEnv(targetDir),
150
- );
151
- extras.hermes.repertoire = {
152
- command: launch.command,
153
- args: launch.args,
154
- env: launch.env,
155
- };
156
- extras.opencode.repertoire = {
157
- type: "local",
158
- command: launch.commandList,
159
- enabled: true,
160
- environment: launch.env,
161
- };
162
- extras.openclaw.repertoire = {
163
- command: launch.command,
164
- args: launch.args,
165
- env: launch.env,
166
- };
164
+ if (repertoireMcp) {
165
+ const launch = wrapScopedLaunch(
166
+ { command: "node", args: [repertoireMcp] },
167
+ baseScopedEnv(targetDir),
168
+ );
169
+ extras.hermes.repertoire = {
170
+ command: launch.command,
171
+ args: launch.args,
172
+ env: launch.env,
173
+ };
174
+ extras.opencode.repertoire = {
175
+ type: "local",
176
+ command: launch.commandList,
177
+ enabled: true,
178
+ environment: launch.env,
179
+ };
180
+ extras.openclaw.repertoire = {
181
+ command: launch.command,
182
+ args: launch.args,
183
+ env: launch.env,
184
+ };
185
+ }
186
+ const gogglesMcp = resolveGogglesMcp(targetDir);
187
+ if (gogglesMcp) {
188
+ const launch = gogglesScopedLaunch(targetDir, gogglesMcp);
189
+ extras.hermes.goggles = {
190
+ command: launch.command,
191
+ args: launch.args,
192
+ env: launch.env,
193
+ };
194
+ extras.opencode.goggles = {
195
+ type: "local",
196
+ command: launch.commandList,
197
+ enabled: true,
198
+ environment: launch.env,
199
+ };
200
+ extras.openclaw.goggles = {
201
+ command: launch.command,
202
+ args: launch.args,
203
+ env: launch.env,
204
+ };
205
+ }
167
206
  } catch {
168
207
  // best-effort
169
208
  }
@@ -880,6 +919,7 @@ module.exports = {
880
919
  detectConsumerExtraMcpServers,
881
920
  isRepertoirePackageRoot,
882
921
  resolveRepertoireMcp,
922
+ resolveGogglesMcp,
883
923
  resolveRepertoireProvider,
884
924
  isDefaultMemoryRoutingOff,
885
925
  enableMemoryRoutingIfResolves,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-pre-tool",
3
- "version": "4.0.31",
3
+ "version": "4.0.32",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "0xRay constitution + temperament PreToolUse for OpenClaw",
@@ -55,11 +55,11 @@ When `multi_agent_orchestration.lead_dev_mode` is true in `features.json` (defau
55
55
 
56
56
  ## Goggles
57
57
 
58
- Pushed on `feat/goggles-look`. Not merged. Not published. Grok CLI has not shipped this. Wear the local build. From the worn checkout run `node dist/integrations/hooks/goggles-pipeline.mjs`. Do not run `npx 0xray goggles`. If `dist` is older than `src/integrations/hooks/goggles-pipeline.mjs`, build that tree and wear that `dist`. A subagent wears this same local build.
58
+ The command is `0xray look`. `0xray goggles` is the same organ. A pipe keeps the line card. A terminal shows that card as a pane. Chat seats without hooks use the `goggles` MCP tools `look` and `status_lens`. Same organ.
59
59
 
60
60
  Kind 0 is actuality. It compares the list in the file to the same six names in the code. It speaks only when those lists differ. A match is quiet. It does not read the suit that is running. It is not a plate type.
61
61
 
62
- Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked. Calling stays off.
62
+ Kind 1 names one plane: dichotomy, syncopate, synthesis, or loop. The lens holds that plane. An action on that plane is quiet. An action on another plane, or on a pipeline drawing, is stopped. A pipeline drawing is not a scope stop. Ground is not a scope stop. Scope, when named, is ecosystem, part, one flow, or one artifact, and an action wider than that scope is stopped. One kind and one scope level at a time. Two planes, calling, ground, and a pipeline name do not arm the lens. `outer` without one plane is `Name one plane.` After 1 is empty. Nothing is picked.
63
63
 
64
64
  `digest` and `triage` return the card already built by `assemblePlane`. The fields are from, digest, plate, entry, exit, files, skills, setup, teardown, and worn. Empty stays empty. The rows are ground, routing, house, boot, governance, memory-recall, orchestration, processor, and reporting. Those nine are not Kind 0. `digest` fills the card. Entry and exit are the plate's input and output box titles when that plate has them. Setup and teardown stay empty unless the row names them. The mill path is `scripts/foundry`. `triage` adds the empty fields and whether the row holds. Zoom after the card is the same ladder: ecosystem, part, one flow, one artifact. One file is one artifact. Several files are not narrowed to a guessed file.
65
65
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./features.schema.json",
3
- "version": "4.0.31",
3
+ "version": "4.0.32",
4
4
  "description": "0xRay Framework - Unified Feature Configuration",
5
5
  "suit_temperament": {
6
6
  "profile": "auto",