graphlin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.codex-plugin/plugin.json +29 -0
  3. package/.mcp.json +9 -0
  4. package/LICENSE +21 -0
  5. package/README.md +71 -0
  6. package/adapters/README.md +32 -0
  7. package/adapters/claude/hooks.json +10 -0
  8. package/adapters/claude/profile.json +18 -0
  9. package/adapters/codex/hooks.json +9 -0
  10. package/adapters/codex/profile.json +22 -0
  11. package/adapters/kiro/profile.json +8 -0
  12. package/mcp.json +11 -0
  13. package/package.json +114 -0
  14. package/plugin.json +20 -0
  15. package/runtime/collector/index.mjs +23 -0
  16. package/runtime/core/candidates.mjs +300 -0
  17. package/runtime/core/common.mjs +69 -0
  18. package/runtime/core/evidence.mjs +150 -0
  19. package/runtime/core/graph.mjs +398 -0
  20. package/runtime/core/index.mjs +4 -0
  21. package/runtime/core/lexical.mjs +255 -0
  22. package/runtime/core/privacy.mjs +206 -0
  23. package/runtime/core/tool-discovery.mjs +122 -0
  24. package/runtime/daemon/auth.mjs +50 -0
  25. package/runtime/daemon/connection-info.mjs +249 -0
  26. package/runtime/daemon/demo.mjs +195 -0
  27. package/runtime/daemon/diagnostics.mjs +404 -0
  28. package/runtime/daemon/export.mjs +7 -0
  29. package/runtime/daemon/ipc.mjs +28 -0
  30. package/runtime/daemon/lock.mjs +137 -0
  31. package/runtime/daemon/manager.mjs +320 -0
  32. package/runtime/daemon/paths.mjs +108 -0
  33. package/runtime/daemon/persistence.mjs +64 -0
  34. package/runtime/daemon/server.mjs +292 -0
  35. package/runtime/daemon/settings.mjs +103 -0
  36. package/runtime/jev/fixture.mjs +99 -0
  37. package/runtime/jev/index.mjs +784 -0
  38. package/runtime/jev/questions.mjs +268 -0
  39. package/runtime/jev/wire.mjs +152 -0
  40. package/runtime/pipeline.mjs +1071 -0
  41. package/runtime/web/app.js +2596 -0
  42. package/runtime/web/index.html +265 -0
  43. package/runtime/web/layout.js +336 -0
  44. package/runtime/web/sidebar.js +525 -0
  45. package/runtime/web/sketch.js +347 -0
  46. package/runtime/web/style.css +593 -0
  47. package/schemas/bundle.schema.json +243 -0
  48. package/schemas/event.schema.json +108 -0
  49. package/schemas/graph.schema.json +449 -0
  50. package/schemas/patch.schema.json +111 -0
  51. package/scripts/arguments.mjs +37 -0
  52. package/scripts/build-packages.mjs +160 -0
  53. package/scripts/collect.sh +23 -0
  54. package/scripts/collector.mjs +11 -0
  55. package/scripts/control.mjs +80 -0
  56. package/scripts/daemon.mjs +28 -0
  57. package/scripts/graphlin.mjs +112 -0
  58. package/scripts/onboarding.mjs +413 -0
  59. package/scripts/validate-packages.mjs +118 -0
  60. package/skills/graphlin/SKILL.md +103 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "graphlin",
3
+ "version": "0.1.0",
4
+ "description": "Local architecture and activity viewer with passive, bounded event hooks.",
5
+ "author": {
6
+ "name": "Graphlin contributors"
7
+ },
8
+ "hooks": "./adapters/claude/hooks.json",
9
+ "license": "MIT",
10
+ "repository": "https://github.com/royosherove/graphlin",
11
+ "homepage": "https://github.com/royosherove/graphlin"
12
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "graphlin",
3
+ "version": "0.1.0",
4
+ "description": "Local architecture and activity diagrams from observable coding-agent work.",
5
+ "author": {
6
+ "name": "Graphlin contributors"
7
+ },
8
+ "skills": "./skills/",
9
+ "mcpServers": "./.mcp.json",
10
+ "interface": {
11
+ "displayName": "Graphlin",
12
+ "shortDescription": "Live architecture and activity viewer",
13
+ "longDescription": "A local viewer with passive event collection, explicit source-sharing controls, and an offline fixture demo.",
14
+ "developerName": "Graphlin contributors",
15
+ "category": "Productivity",
16
+ "capabilities": [
17
+ "Interactive",
18
+ "Read",
19
+ "Write"
20
+ ],
21
+ "defaultPrompt": [
22
+ "Start Graphlin for this project in metadata-only mode."
23
+ ],
24
+ "brandColor": "#20BFA9"
25
+ },
26
+ "license": "MIT",
27
+ "repository": "https://github.com/royosherove/graphlin",
28
+ "homepage": "https://github.com/royosherove/graphlin"
29
+ }
package/.mcp.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "graphlin": {
4
+ "type": "stdio",
5
+ "command": "node",
6
+ "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/control.mjs"]
7
+ }
8
+ }
9
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Graphlin contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Graphlin
2
+
3
+ Live architecture diagrams while **Claude Code or Codex** explores and builds your code.
4
+
5
+ ![Graphlin showing function calls, a cache write, and a browser-to-service connection](docs/images/graphlin-preview.png)
6
+
7
+ ## Get started
8
+
9
+ **You need:** macOS or Linux, Node.js 22.14+, and Claude Code or Codex CLI.
10
+ Source classification also needs a TypeSafe API key; metadata mode needs no key.
11
+
12
+ ### 1. Start the viewer
13
+
14
+ In your project's terminal:
15
+
16
+ ```sh
17
+ npx --yes graphlin@latest
18
+ ```
19
+
20
+ Graphlin asks which host to install, offers **source or metadata** mode for this
21
+ project, and accepts your key at a **masked prompt** if needed. It stores the key
22
+ privately and builds stable plugins outside the npm cache. The browser opens
23
+ automatically. Keep this terminal running; **Ctrl+C** stops the viewer.
24
+
25
+ Source mode permits locally filtered source excerpts, user prompts, and public
26
+ agent messages to be sent to TypeSafe for classification.
27
+
28
+ ### 2. Start your agent
29
+
30
+ Open a **second terminal in the same project**. Choose one:
31
+
32
+ **Claude Code**
33
+
34
+ ```sh
35
+ claude
36
+ ```
37
+
38
+ **Codex**
39
+
40
+ ```sh
41
+ codex
42
+ ```
43
+
44
+ Accept the host's project trust prompt. In Claude, use **`/plugin`** to confirm
45
+ Graphlin is enabled. In Codex, use **`/hooks`** to review and trust Graphlin's
46
+ hooks. Start a new agent session after installation. Then ask either agent:
47
+
48
+ > Orient yourself in this project: read its main files and explain how the components connect.
49
+
50
+ Watch the diagram populate as it explores. On later runs, repeat step 1 and
51
+ launch your agent; Graphlin reuses the saved setup. Use the printed agent command
52
+ if you selected a custom data directory.
53
+
54
+ For setup alone, append `init` to the command. Append `uninstall` to remove
55
+ Graphlin's host plugins across projects while keeping your saved key and history.
56
+
57
+ ## Just looking?
58
+
59
+ Try the offline demo—no key or agent required:
60
+
61
+ ```sh
62
+ npx --yes graphlin@latest demo
63
+ ```
64
+
65
+ ## More
66
+
67
+ [User guide](docs/usage.md) · [Design](docs/graphlin-design.md) ·
68
+ [Contributing](CONTRIBUTING.md) · [Releasing](docs/releasing.md) · [MIT license](LICENSE)
69
+
70
+ Graphlin visualizes observable actions and code evidence. It does not capture
71
+ private reasoning or prove runtime connectivity. Development preview.
@@ -0,0 +1,32 @@
1
+ # Adapter scope
2
+
3
+ The root `plugin.json` and `mcp.json` follow Agent Plugins 1.0.0. OpenAI
4
+ presentation and the Codex hook path are under `extensions.com.openai`.
5
+ `.codex-plugin/plugin.json` remains a compatibility manifest for skill validation.
6
+ The native Claude manifest references its own hook profile and `.mcp.json`
7
+ uses Claude's root-variable substitution.
8
+
9
+ `scripts/build-packages.mjs` generates self-contained `portable/graphlin`,
10
+ `claude/graphlin`, and `codex/graphlin` directories from this source tree.
11
+ No package contains machine-specific paths, a credential, installed config,
12
+ or a marketplace. The portable package contains skills and an MCP server;
13
+ native hook behavior is an extension, not a portable observation guarantee.
14
+
15
+ All hook commands call the same guarded POSIX launcher with their host name.
16
+ They read a bounded JSON input, resolve its `cwd` to the canonical worktree,
17
+ handoff through a private Unix socket, and exit 0 with no output. The collector
18
+ does not make HTTP requests, start a daemon, write raw spools, or change agent
19
+ inputs, outputs, permissions, or stop behavior. A stopped daemon loses events;
20
+ no recovery claim is made for events that never reached it.
21
+
22
+ Node.js 22+ and macOS/Linux are the implemented runtime profile. Windows
23
+ private-pipe ACLs and launch behavior are not certified. Default data is
24
+ `~/.local/state/graphlin`, shared by host adapters. Use the same
25
+ `GRAPHLIN_DATA_DIR` in hooks and controls for an override.
26
+
27
+ Package tests exercise fixtures, launcher outages, local IPC, MCP, and relocated
28
+ package paths. They do not certify that host hooks are loaded, trusted, or active.
29
+
30
+ Kiro has only an inactive experimental profile. The builder emits that profile
31
+ and this scope note, not an active Kiro plugin or hooks. Nothing in this
32
+ repository installs or edits the user's host configuration.
@@ -0,0 +1,10 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [{"hooks":[{"type":"command","command":"if [ -r \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" claude; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
4
+ "PreToolUse": [{"matcher":".*","hooks":[{"type":"command","command":"if [ -r \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" claude; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
5
+ "PostToolUse": [{"matcher":".*","hooks":[{"type":"command","command":"if [ -r \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" claude; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
6
+ "PostToolUseFailure": [{"matcher":".*","hooks":[{"type":"command","command":"if [ -r \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" claude; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
7
+ "SessionStart": [{"hooks":[{"type":"command","command":"if [ -r \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" claude; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
8
+ "Stop": [{"hooks":[{"type":"command","command":"if [ -r \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${CLAUDE_PLUGIN_ROOT}/scripts/collect.sh\" claude; fi >/dev/null 2>&1; exit 0","timeout":2}]}]
9
+ }
10
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "host": "claude",
3
+ "platforms": [
4
+ "darwin",
5
+ "linux"
6
+ ],
7
+ "events": [
8
+ "UserPromptSubmit",
9
+ "PreToolUse",
10
+ "PostToolUse",
11
+ "PostToolUseFailure",
12
+ "SessionStart",
13
+ "Stop"
14
+ ],
15
+ "activation": "not_verified",
16
+ "capture": "bounded_private_ipc",
17
+ "automaticDaemonStart": false
18
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "hooks": {
3
+ "UserPromptSubmit": [{"hooks":[{"type":"command","command":"if [ -r \"${PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${PLUGIN_ROOT}/scripts/collect.sh\" codex; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
4
+ "PreToolUse": [{"matcher":".*","hooks":[{"type":"command","command":"if [ -r \"${PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${PLUGIN_ROOT}/scripts/collect.sh\" codex; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
5
+ "PostToolUse": [{"matcher":".*","hooks":[{"type":"command","command":"if [ -r \"${PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${PLUGIN_ROOT}/scripts/collect.sh\" codex; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
6
+ "SessionStart": [{"hooks":[{"type":"command","command":"if [ -r \"${PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${PLUGIN_ROOT}/scripts/collect.sh\" codex; fi >/dev/null 2>&1; exit 0","timeout":2}]}],
7
+ "Stop": [{"hooks":[{"type":"command","command":"if [ -r \"${PLUGIN_ROOT}/scripts/collect.sh\" ]; then /bin/sh \"${PLUGIN_ROOT}/scripts/collect.sh\" codex; fi >/dev/null 2>&1; exit 0","timeout":2}]}]
8
+ }
9
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "host": "codex",
3
+ "platforms": [
4
+ "darwin",
5
+ "linux"
6
+ ],
7
+ "events": [
8
+ "UserPromptSubmit",
9
+ "PreToolUse",
10
+ "PostToolUse",
11
+ "SessionStart",
12
+ "Stop"
13
+ ],
14
+ "activation": "not_verified",
15
+ "requiresHookTrust": true,
16
+ "coverageGaps": [
17
+ "hosted_tools",
18
+ "repeated_write_stdin",
19
+ "private_reasoning",
20
+ "unimplemented_streaming_events"
21
+ ]
22
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "host": "kiro",
3
+ "enabled": false,
4
+ "experimental": true,
5
+ "activation": "not_certified",
6
+ "events": [],
7
+ "reason": "IDE and CLI trigger/payload and passive-launch contracts require separate release-specific certification."
8
+ }
package/mcp.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
3
+ "mcpServers": {
4
+ "graphlin": {
5
+ "type": "stdio",
6
+ "command": "node",
7
+ "args": ["${PLUGIN_ROOT}/scripts/control.mjs"],
8
+ "cwd": "${PLUGIN_DATA}"
9
+ }
10
+ }
11
+ }
package/package.json ADDED
@@ -0,0 +1,114 @@
1
+ {
2
+ "name": "graphlin",
3
+ "version": "0.1.0",
4
+ "description": "Live architecture and activity diagrams from observable coding-agent work.",
5
+ "private": false,
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.14.0"
9
+ },
10
+ "bin": {
11
+ "graphlin": "scripts/graphlin.mjs"
12
+ },
13
+ "scripts": {
14
+ "start": "node scripts/graphlin.mjs start",
15
+ "demo": "node scripts/graphlin.mjs demo",
16
+ "test": "node --test --test-timeout=30000",
17
+ "build": "node scripts/build-packages.mjs",
18
+ "doctor": "node scripts/graphlin.mjs doctor",
19
+ "eval:jev": "node scripts/evaluate-jev.mjs",
20
+ "eval:discovery": "node scripts/evaluate-discovery.mjs",
21
+ "validate": "node scripts/validate-packages.mjs",
22
+ "prepack": "npm run validate",
23
+ "release:check": "node .github/scripts/verify-release.mjs",
24
+ "pack:check": "node .github/scripts/verify-pack.mjs",
25
+ "check:packages": "npm run validate && npm run build && npm run pack:check"
26
+ },
27
+ "license": "MIT",
28
+ "author": "Graphlin contributors",
29
+ "homepage": "https://github.com/royosherove/graphlin#readme",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/royosherove/graphlin.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/royosherove/graphlin/issues"
36
+ },
37
+ "keywords": [
38
+ "architecture",
39
+ "coding-agents",
40
+ "visualization",
41
+ "mcp",
42
+ "claude",
43
+ "codex"
44
+ ],
45
+ "os": [
46
+ "darwin",
47
+ "linux"
48
+ ],
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org/"
52
+ },
53
+ "files": [
54
+ "./LICENSE",
55
+ "./README.md",
56
+ "./plugin.json",
57
+ "./mcp.json",
58
+ "./.mcp.json",
59
+ "./.claude-plugin/plugin.json",
60
+ "./.codex-plugin/plugin.json",
61
+ "./adapters/README.md",
62
+ "./adapters/claude/hooks.json",
63
+ "./adapters/claude/profile.json",
64
+ "./adapters/codex/hooks.json",
65
+ "./adapters/codex/profile.json",
66
+ "./adapters/kiro/profile.json",
67
+ "./skills/graphlin/SKILL.md",
68
+ "./runtime/collector/index.mjs",
69
+ "./runtime/core/candidates.mjs",
70
+ "./runtime/core/common.mjs",
71
+ "./runtime/core/evidence.mjs",
72
+ "./runtime/core/graph.mjs",
73
+ "./runtime/core/index.mjs",
74
+ "./runtime/core/lexical.mjs",
75
+ "./runtime/core/privacy.mjs",
76
+ "./runtime/core/tool-discovery.mjs",
77
+ "./runtime/daemon/auth.mjs",
78
+ "./runtime/daemon/connection-info.mjs",
79
+ "./runtime/daemon/demo.mjs",
80
+ "./runtime/daemon/diagnostics.mjs",
81
+ "./runtime/daemon/export.mjs",
82
+ "./runtime/daemon/ipc.mjs",
83
+ "./runtime/daemon/lock.mjs",
84
+ "./runtime/daemon/manager.mjs",
85
+ "./runtime/daemon/paths.mjs",
86
+ "./runtime/daemon/persistence.mjs",
87
+ "./runtime/daemon/server.mjs",
88
+ "./runtime/daemon/settings.mjs",
89
+ "./runtime/jev/fixture.mjs",
90
+ "./runtime/jev/index.mjs",
91
+ "./runtime/jev/questions.mjs",
92
+ "./runtime/jev/wire.mjs",
93
+ "./runtime/pipeline.mjs",
94
+ "./runtime/web/app.js",
95
+ "./runtime/web/index.html",
96
+ "./runtime/web/layout.js",
97
+ "./runtime/web/sidebar.js",
98
+ "./runtime/web/sketch.js",
99
+ "./runtime/web/style.css",
100
+ "./schemas/bundle.schema.json",
101
+ "./schemas/event.schema.json",
102
+ "./schemas/graph.schema.json",
103
+ "./schemas/patch.schema.json",
104
+ "./scripts/arguments.mjs",
105
+ "./scripts/build-packages.mjs",
106
+ "./scripts/collect.sh",
107
+ "./scripts/collector.mjs",
108
+ "./scripts/control.mjs",
109
+ "./scripts/daemon.mjs",
110
+ "./scripts/graphlin.mjs",
111
+ "./scripts/onboarding.mjs",
112
+ "./scripts/validate-packages.mjs"
113
+ ]
114
+ }
package/plugin.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "graphlin",
4
+ "version": "0.1.0",
5
+ "description": "Local architecture and activity diagrams from observable coding-agent work.",
6
+ "extensions": {
7
+ "com.openai": {
8
+ "interface": {
9
+ "displayName": "Graphlin",
10
+ "shortDescription": "Live architecture and activity viewer",
11
+ "longDescription": "A local viewer with passive event collection, explicit source-sharing controls, and an offline fixture demo.",
12
+ "developerName": "Graphlin contributors",
13
+ "category": "Productivity",
14
+ "defaultPrompt": ["Start Graphlin for this project in metadata-only mode."],
15
+ "brandColor": "#20BFA9"
16
+ },
17
+ "hooks": "./adapters/codex/hooks.json"
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,23 @@
1
+ import { projectPaths, MAX_IPC_BYTES } from '../daemon/paths.mjs';
2
+ import { requestIPC } from '../daemon/ipc.mjs';
3
+
4
+ export async function collect(payload, { host = 'claude', dataDir, timeoutMs = 200 } = {}) {
5
+ if (!['claude', 'codex', 'kiro'].includes(host) || !payload || Array.isArray(payload) ||
6
+ typeof payload !== 'object' || typeof payload.cwd !== 'string') return false;
7
+ try {
8
+ const paths = await projectPaths(payload.cwd, dataDir);
9
+ const reply = await requestIPC(paths.socket, { host, payload }, { timeoutMs });
10
+ return reply.ok === true;
11
+ } catch { return false; }
12
+ }
13
+
14
+ export async function readHook(stream = process.stdin) {
15
+ const parts = []; let bytes = 0;
16
+ for await (const chunk of stream) {
17
+ bytes += chunk.length;
18
+ if (bytes > MAX_IPC_BYTES - 256) return null;
19
+ parts.push(chunk);
20
+ }
21
+ try { return JSON.parse(Buffer.concat(parts).toString('utf8')); }
22
+ catch { return null; }
23
+ }