opencontext-mcp 1.2.1 → 1.3.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 (73) hide show
  1. package/README.md +38 -12
  2. package/dist/cli/index.d.ts +25 -0
  3. package/dist/cli/index.js +97 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/dist/{config.d.ts → config/index.d.ts} +1 -1
  6. package/dist/config/index.js +62 -0
  7. package/dist/config/index.js.map +1 -0
  8. package/dist/index.js +52 -9
  9. package/dist/index.js.map +1 -1
  10. package/dist/init/constants.d.ts +6 -0
  11. package/dist/init/constants.js +17 -0
  12. package/dist/init/constants.js.map +1 -0
  13. package/dist/init/display.d.ts +9 -0
  14. package/dist/init/display.js +28 -0
  15. package/dist/init/display.js.map +1 -0
  16. package/dist/init/files.d.ts +12 -0
  17. package/dist/init/files.js +39 -0
  18. package/dist/init/files.js.map +1 -0
  19. package/dist/init/index.d.ts +6 -0
  20. package/dist/init/index.js +157 -0
  21. package/dist/init/index.js.map +1 -0
  22. package/dist/init/inspect.d.ts +21 -0
  23. package/dist/init/inspect.js +57 -0
  24. package/dist/init/inspect.js.map +1 -0
  25. package/dist/init/integrations/claude.d.ts +17 -0
  26. package/dist/init/integrations/claude.js +94 -0
  27. package/dist/init/integrations/claude.js.map +1 -0
  28. package/dist/init/integrations/opencode.d.ts +20 -0
  29. package/dist/init/integrations/opencode.js +108 -0
  30. package/dist/init/integrations/opencode.js.map +1 -0
  31. package/dist/init/integrations/workflow-files.d.ts +23 -0
  32. package/dist/init/integrations/workflow-files.js +102 -0
  33. package/dist/init/integrations/workflow-files.js.map +1 -0
  34. package/dist/init/prompts.d.ts +9 -0
  35. package/dist/init/prompts.js +17 -0
  36. package/dist/init/prompts.js.map +1 -0
  37. package/dist/server/http.d.ts +21 -0
  38. package/dist/server/http.js +63 -0
  39. package/dist/server/http.js.map +1 -0
  40. package/dist/{server.d.ts → server/index.d.ts} +3 -1
  41. package/dist/{server.js → server/index.js} +7 -4
  42. package/dist/server/index.js.map +1 -0
  43. package/dist/shared/constants.d.ts +27 -0
  44. package/dist/shared/constants.js +26 -0
  45. package/dist/shared/constants.js.map +1 -0
  46. package/dist/shared/errors.d.ts +17 -0
  47. package/dist/shared/errors.js +28 -0
  48. package/dist/shared/errors.js.map +1 -0
  49. package/dist/shared/types.d.ts +29 -0
  50. package/dist/shared/types.js +17 -0
  51. package/dist/shared/types.js.map +1 -0
  52. package/dist/{context-store.d.ts → store/context-store.d.ts} +2 -16
  53. package/dist/store/context-store.js +147 -0
  54. package/dist/store/context-store.js.map +1 -0
  55. package/dist/store/index-builder.d.ts +32 -0
  56. package/dist/store/index-builder.js +162 -0
  57. package/dist/store/index-builder.js.map +1 -0
  58. package/dist/validation/index.d.ts +16 -0
  59. package/dist/validation/index.js +39 -0
  60. package/dist/validation/index.js.map +1 -0
  61. package/dist/{validation.d.ts → validation/write-guard.d.ts} +0 -16
  62. package/dist/{validation.js → validation/write-guard.js} +2 -38
  63. package/dist/validation/write-guard.js.map +1 -0
  64. package/package.json +2 -2
  65. package/dist/config.js +0 -145
  66. package/dist/config.js.map +0 -1
  67. package/dist/context-store.js +0 -296
  68. package/dist/context-store.js.map +0 -1
  69. package/dist/server.js.map +0 -1
  70. package/dist/types.d.ts +0 -56
  71. package/dist/types.js +0 -63
  72. package/dist/types.js.map +0 -1
  73. package/dist/validation.js.map +0 -1
package/README.md CHANGED
@@ -22,18 +22,36 @@ No vector databases, no cloud subscriptions, and no hidden state. Memory is plai
22
22
 
23
23
  ### Quickstart
24
24
 
25
- Run directly without installation via `npx`:
25
+ Run the MCP server directly without installation via `npx`:
26
26
 
27
27
  ```bash
28
28
  npx -y opencontext-mcp
29
29
 
30
30
  ```
31
31
 
32
+ ### One-Command Setup
33
+
34
+ Scaffold OpenContext in the current project interactively:
35
+
36
+ ```bash
37
+ npx -y opencontext-mcp init
38
+ ```
39
+
40
+ `init` walks you through the setup (enabling OpenCode and Claude Code integration) and generates everything you need:
41
+
42
+ - `.opencontext/` — directory that holds your context topic files
43
+ - `.opencontext.json` — configuration template
44
+ - `opencode.json` — MCP server entry for OpenCode
45
+ - `.mcp.json` — MCP server entry for Claude Code
46
+ - `AGENTS.md` / `CLAUDE.md` — workflow reminders for your agents
47
+
48
+ The `init` command takes no arguments: it always runs in the current directory, prompts interactively, and never overwrites an existing config.
49
+
32
50
  ### Client Setup
33
51
 
34
52
  #### OpenCode
35
53
 
36
- Add OpenContext to your project MCP configuration (`opencode.json`):
54
+ Add OpenContext to your project MCP configuration (`opencode.json`) — or let `opencontext-mcp init` do it for you:
37
55
 
38
56
  ```json
39
57
  {
@@ -66,6 +84,22 @@ Add OpenContext to your MCP settings file (`claude_desktop_config.json` or Curso
66
84
 
67
85
  ---
68
86
 
87
+ ### Remote Access (HTTP)
88
+
89
+ Expose the MCP server over the network with the Streamable HTTP transport. The endpoint URL is printed to stderr on startup.
90
+
91
+ ```bash
92
+ # Plain HTTP on 127.0.0.1:3032 (default)
93
+ opencontext-mcp --http
94
+
95
+ # Custom port / bind to all interfaces
96
+ opencontext-mcp server --http --port 8787 --host 0.0.0.0
97
+ ```
98
+
99
+ The server listens at `http://<host>:<port>/mcp` (stateless Streamable HTTP — one request at a time, no sessions). `GET /` returns basic server info, handy for a browser health check.
100
+
101
+ ---
102
+
69
103
  ### Core Tools
70
104
 
71
105
  | Tool | Parameters | Description |
@@ -121,27 +155,19 @@ When a topic becomes obsolete, call `delete_context` to remove it. For deprecate
121
155
 
122
156
  ### Configuration
123
157
 
124
- Customize storage paths and security boundaries with an optional `.opencontext.jsonc` file in your repository root:
158
+ Customize storage paths and security boundaries with an optional `.opencontext.json` file in your repository root (plain JSON — comments are not supported):
125
159
 
126
- ```jsonc
160
+ ```json
127
161
  {
128
- // Storage location (default: ".opencontext")
129
162
  "path": ".opencontext",
130
-
131
- // Prevent agents from writing or updating files
132
163
  "readOnly": false,
133
-
134
- // Auto-generate index.md with topic descriptions
135
164
  "autoIndex": true,
136
-
137
- // Write guard & prompt injection defenses
138
165
  "guard": {
139
166
  "enabled": true,
140
167
  "maxFileSizeKb": 50,
141
168
  "strictPatternCheck": true
142
169
  }
143
170
  }
144
-
145
171
  ```
146
172
 
147
173
  ---
@@ -0,0 +1,25 @@
1
+ export interface ServerCliArgs {
2
+ command: "server";
3
+ transport: "stdio" | "http";
4
+ port: number;
5
+ host: string;
6
+ }
7
+ export type CliArgs = ServerCliArgs | {
8
+ command: "init";
9
+ } | {
10
+ command: "help";
11
+ } | {
12
+ command: "version";
13
+ };
14
+ export declare class CliError extends Error {
15
+ constructor(message: string);
16
+ }
17
+ /**
18
+ * Parses command-line arguments into a structured CliArgs object.
19
+ * @param argv - Raw process.argv (defaults to process.argv)
20
+ * @returns Parsed arguments
21
+ * @throws CliError on invalid arguments
22
+ */
23
+ export declare function parseArgs(argv?: string[]): CliArgs;
24
+ export declare function printHelp(): void;
25
+ export declare function printVersion(): void;
@@ -0,0 +1,97 @@
1
+ import { SERVER_VERSION, DEFAULT_HTTP_HOST, DEFAULT_HTTP_PORT, } from "../shared/constants.js";
2
+ export class CliError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "CliError";
6
+ }
7
+ }
8
+ const USAGE = `Usage: opencontext-mcp [command] [options]
9
+
10
+ Commands:
11
+ (default) Start the MCP server (stdio transport)
12
+ server Start the MCP server (alias of the default)
13
+ init Initialize OpenContext in the current directory
14
+ --help, -h Show this help message
15
+ --version, -v Show version number
16
+
17
+ Server options:
18
+ --http Serve over HTTP using the Streamable HTTP transport
19
+ --port <port> HTTP port (default: 3032)
20
+ --host <host> Interface to bind (default: 127.0.0.1)
21
+
22
+ Examples:
23
+ opencontext-mcp Start MCP server over stdio
24
+ opencontext-mcp --http Serve over HTTP at http://127.0.0.1:3032/mcp
25
+ opencontext-mcp server --http --port 8787`;
26
+ /**
27
+ * Parses command-line arguments into a structured CliArgs object.
28
+ * @param argv - Raw process.argv (defaults to process.argv)
29
+ * @returns Parsed arguments
30
+ * @throws CliError on invalid arguments
31
+ */
32
+ export function parseArgs(argv = process.argv) {
33
+ const args = argv.slice(2);
34
+ if (args.length === 0) {
35
+ return { command: "server", transport: "stdio", port: DEFAULT_HTTP_PORT, host: DEFAULT_HTTP_HOST };
36
+ }
37
+ const first = args[0];
38
+ if (first === undefined) {
39
+ return { command: "server", transport: "stdio", port: DEFAULT_HTTP_PORT, host: DEFAULT_HTTP_HOST };
40
+ }
41
+ if (first === "--help" || first === "-h") {
42
+ return { command: "help" };
43
+ }
44
+ if (first === "--version" || first === "-v") {
45
+ return { command: "version" };
46
+ }
47
+ if (first === "init") {
48
+ return { command: "init" };
49
+ }
50
+ let index = first === "server" ? 1 : 0;
51
+ let transport = "stdio";
52
+ let port = DEFAULT_HTTP_PORT;
53
+ let host = DEFAULT_HTTP_HOST;
54
+ while (index < args.length) {
55
+ const arg = args[index];
56
+ if (arg === undefined) {
57
+ break;
58
+ }
59
+ switch (arg) {
60
+ case "--http":
61
+ transport = "http";
62
+ index += 1;
63
+ break;
64
+ case "--port": {
65
+ const value = args[index + 1];
66
+ if (value === undefined || !/^\d+$/.test(value)) {
67
+ throw new CliError("--port requires a numeric value");
68
+ }
69
+ port = Number(value);
70
+ index += 2;
71
+ break;
72
+ }
73
+ case "--host": {
74
+ const value = args[index + 1];
75
+ if (value === undefined) {
76
+ throw new CliError("--host requires a value");
77
+ }
78
+ host = value;
79
+ index += 2;
80
+ break;
81
+ }
82
+ default:
83
+ if (arg.startsWith("-")) {
84
+ throw new CliError(`Unknown option: ${arg}\n\nRun "opencontext-mcp --help" for usage.`);
85
+ }
86
+ throw new CliError(`Unknown command: ${arg}\n\nRun "opencontext-mcp --help" for usage.`);
87
+ }
88
+ }
89
+ return { command: "server", transport, port, host };
90
+ }
91
+ export function printHelp() {
92
+ process.stdout.write(USAGE + "\n");
93
+ }
94
+ export function printVersion() {
95
+ process.stdout.write(SERVER_VERSION + "\n");
96
+ }
97
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ export interface OpenContextConfig {
18
18
  export type ResolvedConfig = Required<Omit<OpenContextConfig, "$schema">>;
19
19
  export declare const DEFAULT_CONFIG: ResolvedConfig;
20
20
  /**
21
- * Loads configuration from .opencontext.jsonc or .opencontext.json in the given directory.
21
+ * Loads configuration from .opencontext.json in the given directory.
22
22
  * Falls back to DEFAULT_CONFIG if no file exists or parsing fails.
23
23
  */
24
24
  export declare function loadConfig(cwd?: string): Promise<ResolvedConfig>;
@@ -0,0 +1,62 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ export const DEFAULT_CONFIG = {
4
+ path: ".opencontext",
5
+ readOnly: false,
6
+ disabled: false,
7
+ autoIndex: true,
8
+ history: {
9
+ enabled: false,
10
+ maxBackupsPerTopic: 5,
11
+ retentionDays: 7,
12
+ },
13
+ guard: {
14
+ enabled: true,
15
+ maxFileSizeKb: 50,
16
+ strictPatternCheck: true,
17
+ },
18
+ };
19
+ const CONFIG_FILENAME = ".opencontext.json";
20
+ /**
21
+ * Merges a partial config object into a full one, recursing into nested
22
+ * objects (e.g. history, guard) instead of replacing them wholesale.
23
+ */
24
+ function deepMerge(target, source) {
25
+ const result = { ...target };
26
+ for (const key of Object.keys(source)) {
27
+ const sourceVal = source[key];
28
+ const targetVal = result[key];
29
+ if (sourceVal !== null &&
30
+ typeof sourceVal === "object" &&
31
+ !Array.isArray(sourceVal) &&
32
+ targetVal !== null &&
33
+ typeof targetVal === "object" &&
34
+ !Array.isArray(targetVal)) {
35
+ result[key] = deepMerge(targetVal, sourceVal);
36
+ }
37
+ else if (sourceVal !== undefined) {
38
+ result[key] = sourceVal;
39
+ }
40
+ }
41
+ return result;
42
+ }
43
+ /**
44
+ * Loads configuration from .opencontext.json in the given directory.
45
+ * Falls back to DEFAULT_CONFIG if no file exists or parsing fails.
46
+ */
47
+ export async function loadConfig(cwd = process.cwd()) {
48
+ const filePath = path.join(cwd, CONFIG_FILENAME);
49
+ try {
50
+ const raw = await readFile(filePath, "utf8");
51
+ const parsed = JSON.parse(raw);
52
+ return deepMerge(DEFAULT_CONFIG, parsed);
53
+ }
54
+ catch (error) {
55
+ if (error.code === "ENOENT") {
56
+ return DEFAULT_CONFIG;
57
+ }
58
+ console.error(`OpenContext: failed to parse ${CONFIG_FILENAME}: ${error.message}`);
59
+ return DEFAULT_CONFIG;
60
+ }
61
+ }
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAsBlC,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,KAAK;IACf,SAAS,EAAE,IAAI;IACf,OAAO,EAAE;QACP,OAAO,EAAE,KAAK;QACd,kBAAkB,EAAE,CAAC;QACrB,aAAa,EAAE,CAAC;KACjB;IACD,KAAK,EAAE;QACL,OAAO,EAAE,IAAI;QACb,aAAa,EAAE,EAAE;QACjB,kBAAkB,EAAE,IAAI;KACzB;CACF,CAAC;AAEF,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAE5C;;;GAGG;AACH,SAAS,SAAS,CAAoC,MAAS,EAAE,MAAkB;IACjF,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAmB,EAAE,CAAC;QACxD,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9B,IACE,SAAS,KAAK,IAAI;YAClB,OAAO,SAAS,KAAK,QAAQ;YAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;YACzB,SAAS,KAAK,IAAI;YAClB,OAAO,SAAS,KAAK,QAAQ;YAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EACzB,CAAC;YACA,MAAkC,CAAC,GAAa,CAAC,GAAG,SAAS,CAC5D,SAAoC,EACpC,SAAoC,CACrC,CAAC;QACJ,CAAC;aAAM,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,GAAG,SAA0B,CAAC;QAC3C,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEjD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;QACpD,OAAO,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,gCAAgC,eAAe,KAAM,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9F,OAAO,cAAc,CAAC;IACxB,CAAC;AACH,CAAC"}
package/dist/index.js CHANGED
@@ -1,19 +1,62 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { createOpenContextServer } from "./server.js";
4
- import { SERVER_NAME, SERVER_VERSION, getErrorMessage } from "./types.js";
3
+ import { createOpenContextServer, startHttpServer } from "./server/index.js";
4
+ import { SERVER_NAME, SERVER_VERSION } from "./shared/constants.js";
5
+ import { getErrorMessage } from "./shared/errors.js";
6
+ import { parseArgs, printHelp, printVersion, CliError } from "./cli/index.js";
7
+ import { runInit } from "./init/index.js";
5
8
  /**
6
- * Main entry point for the OpenContext MCP server.
7
- * Creates server instance and connects to stdio transport.
9
+ * Main entry point for the OpenContext CLI.
10
+ * Routes to the appropriate command based on CLI arguments.
8
11
  */
9
12
  async function main() {
10
- const server = await createOpenContextServer();
11
- const transport = new StdioServerTransport();
12
- await server.connect(transport);
13
- if (process.env.OPENCONTEXT_TELEMETRY_DISABLED !== "1" && process.env.DO_NOT_TRACK !== "1") {
14
- process.stderr.write(`[opencontext] v${SERVER_VERSION} active. https://github.com/slxca/opencontext\n`);
13
+ let args;
14
+ try {
15
+ args = parseArgs();
16
+ }
17
+ catch (error) {
18
+ if (error instanceof CliError) {
19
+ process.stderr.write(`Error: ${error.message}\n`);
20
+ process.exit(1);
21
+ }
22
+ throw error;
23
+ }
24
+ switch (args.command) {
25
+ case "help":
26
+ printHelp();
27
+ return;
28
+ case "version":
29
+ printVersion();
30
+ return;
31
+ case "init":
32
+ await runInit();
33
+ return;
34
+ case "server":
35
+ default: {
36
+ if (args.transport === "http") {
37
+ const handle = await startHttpServer({
38
+ host: args.host,
39
+ port: args.port,
40
+ buildServer: () => createOpenContextServer(),
41
+ });
42
+ process.stderr.write(`[opencontext] v${SERVER_VERSION} active. ${handle.url}\n`);
43
+ break;
44
+ }
45
+ const server = await createOpenContextServer();
46
+ const transport = new StdioServerTransport();
47
+ await server.connect(transport);
48
+ if (process.env.OPENCONTEXT_TELEMETRY_DISABLED !== "1" &&
49
+ process.env.DO_NOT_TRACK !== "1") {
50
+ process.stderr.write(`[opencontext] v${SERVER_VERSION} active. https://github.com/slxca/opencontext\n`);
51
+ }
52
+ break;
53
+ }
15
54
  }
16
55
  }
56
+ // Clean exit on SIGINT — no orphaned temp files
57
+ process.on("SIGINT", () => {
58
+ process.exit(0);
59
+ });
17
60
  main().catch((error) => {
18
61
  console.error(`Failed to start ${SERVER_NAME}: ${getErrorMessage(error)}`);
19
62
  process.exit(1);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE1E;;;GAGG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,MAAM,uBAAuB,EAAE,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,IAAI,OAAO,CAAC,GAAG,CAAC,8BAA8B,KAAK,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,GAAG,EAAE,CAAC;QAC3F,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,cAAc,iDAAiD,CAAC,CAAC;IAC1G,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,mBAAmB,WAAW,KAAK,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C;;;GAGG;AACH,KAAK,UAAU,IAAI;IACjB,IAAI,IAAI,CAAC;IACT,IAAI,CAAC;QACH,IAAI,GAAG,SAAS,EAAE,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IAED,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,KAAK,MAAM;YACT,SAAS,EAAE,CAAC;YACZ,OAAO;QAET,KAAK,SAAS;YACZ,YAAY,EAAE,CAAC;YACf,OAAO;QAET,KAAK,MAAM;YACT,MAAM,OAAO,EAAE,CAAC;YAChB,OAAO;QAET,KAAK,QAAQ,CAAC;QACd,OAAO,CAAC,CAAC,CAAC;YACR,IAAI,IAAI,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;gBAC9B,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC;oBACnC,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,WAAW,EAAE,GAAG,EAAE,CAAC,uBAAuB,EAAE;iBAC7C,CAAC,CAAC;gBACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,cAAc,YAAY,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;gBACjF,MAAM;YACR,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,uBAAuB,EAAE,CAAC;YAC/C,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;YAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAEhC,IACE,OAAO,CAAC,GAAG,CAAC,8BAA8B,KAAK,GAAG;gBAClD,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,GAAG,EAChC,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kBAAkB,cAAc,iDAAiD,CAClF,CAAC;YACJ,CAAC;YACD,MAAM;QACR,CAAC;IACH,CAAC;AACH,CAAC;AAED,gDAAgD;AAChD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,mBAAmB,WAAW,KAAK,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ /** Filename of the generated OpenContext config template. */
2
+ export declare const CONFIG_FILENAME = ".opencontext.json";
3
+ /** Directory that holds the context topic files. */
4
+ export declare const CONTEXT_DIR_NAME = ".opencontext";
5
+ /** Template written when no .opencontext.json exists yet. */
6
+ export declare const CONFIG_CONTENT = "{\n \"path\": \".opencontext\",\n \"readOnly\": false,\n \"autoIndex\": true,\n \"guard\": {\n \"enabled\": true,\n \"maxFileSizeKb\": 50,\n \"strictPatternCheck\": true\n }\n}\n";
@@ -0,0 +1,17 @@
1
+ /** Filename of the generated OpenContext config template. */
2
+ export const CONFIG_FILENAME = ".opencontext.json";
3
+ /** Directory that holds the context topic files. */
4
+ export const CONTEXT_DIR_NAME = ".opencontext";
5
+ /** Template written when no .opencontext.json exists yet. */
6
+ export const CONFIG_CONTENT = `{
7
+ "path": ".opencontext",
8
+ "readOnly": false,
9
+ "autoIndex": true,
10
+ "guard": {
11
+ "enabled": true,
12
+ "maxFileSizeKb": 50,
13
+ "strictPatternCheck": true
14
+ }
15
+ }
16
+ `;
17
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/init/constants.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,MAAM,CAAC,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAEnD,oDAAoD;AACpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAE/C,6DAA6D;AAC7D,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;CAU7B,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ConfigStatus } from "../shared/types.js";
2
+ /**
3
+ * Prints a step indicator to stderr.
4
+ * @param label - Step label
5
+ * @param done - Whether to show a checkmark (done) or spinner text
6
+ */
7
+ export declare function printStep(label: string, done?: boolean): void;
8
+ /** Prints a config/generation result line to stderr. */
9
+ export declare function printResult(status: ConfigStatus | "invalid", message: string): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Prints a step indicator to stderr.
3
+ * @param label - Step label
4
+ * @param done - Whether to show a checkmark (done) or spinner text
5
+ */
6
+ export function printStep(label, done = true) {
7
+ const icon = done ? "\x1b[32m✓\x1b[0m" : "\x1b[36m●\x1b[0m";
8
+ process.stderr.write(` ${icon} ${label}\n`);
9
+ }
10
+ /** Maps a config result to a terminal status icon. */
11
+ function statusIcon(status) {
12
+ switch (status) {
13
+ case "created":
14
+ return "\x1b[32m✓\x1b[0m";
15
+ case "updated":
16
+ return "\x1b[33m~ \x1b[0m";
17
+ case "unchanged":
18
+ return "\x1b[36m✓\x1b[0m";
19
+ case "skipped":
20
+ return "\x1b[90m-\x1b[0m";
21
+ }
22
+ }
23
+ /** Prints a config/generation result line to stderr. */
24
+ export function printResult(status, message) {
25
+ const icon = status === "invalid" ? "\x1b[31m!\x1b[0m" : statusIcon(status);
26
+ process.stderr.write(` ${icon} ${message}\n`);
27
+ }
28
+ //# sourceMappingURL=display.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display.js","sourceRoot":"","sources":["../../src/init/display.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,IAAI,GAAG,IAAI;IAClD,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC5D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,sDAAsD;AACtD,SAAS,UAAU,CAAC,MAAoB;IACtC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,SAAS;YACZ,OAAO,kBAAkB,CAAC;QAC5B,KAAK,SAAS;YACZ,OAAO,mBAAmB,CAAC;QAC7B,KAAK,WAAW;YACd,OAAO,kBAAkB,CAAC;QAC5B,KAAK,SAAS;YACZ,OAAO,kBAAkB,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,WAAW,CAAC,MAAgC,EAAE,OAAe;IAC3E,MAAM,IAAI,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,OAAO,IAAI,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Writes a file atomically using a temp-then-rename pattern.
3
+ * @param filePath - Target file path
4
+ * @param content - File content
5
+ */
6
+ export declare function atomicWrite(filePath: string, content: string): Promise<void>;
7
+ /**
8
+ * Validates an existing .opencontext.json file.
9
+ * Leaves the file content untouched; only reports whether it parses.
10
+ * @returns true when the existing config is parseable JSON
11
+ */
12
+ export declare function validateConfigFile(filePath: string): Promise<boolean>;
@@ -0,0 +1,39 @@
1
+ import { readFile, writeFile, unlink, rename } from "node:fs/promises";
2
+ /**
3
+ * Writes a file atomically using a temp-then-rename pattern.
4
+ * @param filePath - Target file path
5
+ * @param content - File content
6
+ */
7
+ export async function atomicWrite(filePath, content) {
8
+ const tmpPath = filePath + ".tmp";
9
+ try {
10
+ await writeFile(tmpPath, content, "utf8");
11
+ await rename(tmpPath, filePath);
12
+ }
13
+ catch (error) {
14
+ // Clean up temp file on failure
15
+ try {
16
+ await unlink(tmpPath);
17
+ }
18
+ catch {
19
+ // Ignore cleanup errors
20
+ }
21
+ throw error;
22
+ }
23
+ }
24
+ /**
25
+ * Validates an existing .opencontext.json file.
26
+ * Leaves the file content untouched; only reports whether it parses.
27
+ * @returns true when the existing config is parseable JSON
28
+ */
29
+ export async function validateConfigFile(filePath) {
30
+ try {
31
+ const raw = await readFile(filePath, "utf8");
32
+ JSON.parse(raw);
33
+ return true;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/init/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB,EAAE,OAAe;IACjE,MAAM,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1C,MAAM,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gCAAgC;QAChC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACvD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Runs the init command.
3
+ * Inspects the current directory, prompts for configuration, and generates files.
4
+ * Existing configs are extended, never overwritten.
5
+ */
6
+ export declare function runInit(): Promise<void>;