@sirius-mcp/mcp 1.4.0 → 1.5.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 (76) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +177 -0
  3. package/dist/adapters/sqlite/ftsIndex.js +17 -17
  4. package/dist/installer/backup.d.ts +4 -0
  5. package/dist/installer/backup.d.ts.map +1 -0
  6. package/dist/installer/backup.js +88 -0
  7. package/dist/installer/backup.js.map +1 -0
  8. package/dist/installer/cli-parser.d.ts +3 -0
  9. package/dist/installer/cli-parser.d.ts.map +1 -0
  10. package/dist/installer/cli-parser.js +71 -0
  11. package/dist/installer/cli-parser.js.map +1 -0
  12. package/dist/installer/cli.d.ts +10 -0
  13. package/dist/installer/cli.d.ts.map +1 -0
  14. package/dist/installer/cli.js +208 -0
  15. package/dist/installer/cli.js.map +1 -0
  16. package/dist/installer/detector.d.ts +8 -0
  17. package/dist/installer/detector.d.ts.map +1 -0
  18. package/dist/installer/detector.js +169 -0
  19. package/dist/installer/detector.js.map +1 -0
  20. package/dist/installer/diff.d.ts +3 -0
  21. package/dist/installer/diff.d.ts.map +1 -0
  22. package/dist/installer/diff.js +85 -0
  23. package/dist/installer/diff.js.map +1 -0
  24. package/dist/installer/dispatch.d.ts +5 -0
  25. package/dist/installer/dispatch.d.ts.map +1 -0
  26. package/dist/installer/dispatch.js +96 -0
  27. package/dist/installer/dispatch.js.map +1 -0
  28. package/dist/installer/index.d.ts +10 -0
  29. package/dist/installer/index.d.ts.map +1 -0
  30. package/dist/installer/index.js +20 -0
  31. package/dist/installer/index.js.map +1 -0
  32. package/dist/installer/patchers/index.d.ts +7 -0
  33. package/dist/installer/patchers/index.d.ts.map +1 -0
  34. package/dist/installer/patchers/index.js +26 -0
  35. package/dist/installer/patchers/index.js.map +1 -0
  36. package/dist/installer/patchers/jsonc.d.ts +4 -0
  37. package/dist/installer/patchers/jsonc.d.ts.map +1 -0
  38. package/dist/installer/patchers/jsonc.js +126 -0
  39. package/dist/installer/patchers/jsonc.js.map +1 -0
  40. package/dist/installer/patchers/toml.d.ts +3 -0
  41. package/dist/installer/patchers/toml.d.ts.map +1 -0
  42. package/dist/installer/patchers/toml.js +88 -0
  43. package/dist/installer/patchers/toml.js.map +1 -0
  44. package/dist/installer/patchers/yaml.d.ts +3 -0
  45. package/dist/installer/patchers/yaml.d.ts.map +1 -0
  46. package/dist/installer/patchers/yaml.js +29 -0
  47. package/dist/installer/patchers/yaml.js.map +1 -0
  48. package/dist/installer/targets-base.d.ts +8 -0
  49. package/dist/installer/targets-base.d.ts.map +1 -0
  50. package/dist/installer/targets-base.js +37 -0
  51. package/dist/installer/targets-base.js.map +1 -0
  52. package/dist/installer/targets-cli.d.ts +3 -0
  53. package/dist/installer/targets-cli.d.ts.map +1 -0
  54. package/dist/installer/targets-cli.js +126 -0
  55. package/dist/installer/targets-cli.js.map +1 -0
  56. package/dist/installer/targets-ide.d.ts +3 -0
  57. package/dist/installer/targets-ide.d.ts.map +1 -0
  58. package/dist/installer/targets-ide.js +180 -0
  59. package/dist/installer/targets-ide.js.map +1 -0
  60. package/dist/installer/targets.d.ts +7 -0
  61. package/dist/installer/targets.d.ts.map +1 -0
  62. package/dist/installer/targets.js +11 -0
  63. package/dist/installer/targets.js.map +1 -0
  64. package/dist/installer/tui.d.ts +8 -0
  65. package/dist/installer/tui.d.ts.map +1 -0
  66. package/dist/installer/tui.js +257 -0
  67. package/dist/installer/tui.js.map +1 -0
  68. package/dist/installer/types.d.ts +95 -0
  69. package/dist/installer/types.d.ts.map +1 -0
  70. package/dist/installer/types.js +7 -0
  71. package/dist/installer/types.js.map +1 -0
  72. package/dist/server.d.ts +1 -0
  73. package/dist/server.d.ts.map +1 -1
  74. package/dist/server.js +11 -1
  75. package/dist/server.js.map +1 -1
  76. package/package.json +69 -67
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Sirius MCP Server License
2
+ Copyright (c) 2026 Sirius MCP Contributors
3
+
4
+ Proprietary Software.
5
+ Free for solo developer and local development use.
6
+ Distribution and enterprise use are subject to commercial licensing terms.
7
+ See https://siriusmcp.dev for more details.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # @sirius-mcp/mcp
2
+
3
+ > **Sirius MCP** — Persistent project-level context memory for AI coding agents.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@sirius-mcp/mcp.svg)](https://www.npmjs.com/package/@sirius-mcp/mcp)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](https://nodejs.org)
7
+ [![MCP](https://img.shields.io/badge/MCP-1.29.0-blue.svg)](https://modelcontextprotocol.io)
8
+
9
+ Sirius is a Model Context Protocol (MCP) server that gives your AI agent (Claude Code, Cursor, Windsurf, OpenCode, VS Code, Antigravity) long-term, auditable project memory. It stores architecture decisions, features, and business rules in a plain-text `.context/` markdown tree directly in your repository, indexed locally with SQLite FTS5.
10
+
11
+ Solo use is completely free with zero sign-up or license key needed.
12
+
13
+ ---
14
+
15
+ ## Quick Start
16
+
17
+ Run or configure Sirius directly using `npx`:
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "sirius": {
23
+ "command": "npx",
24
+ "args": ["-y", "@sirius-mcp/mcp"]
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ `-y` automatically accepts the package installation prompt so the agent startup is non-blocking.
31
+
32
+ ---
33
+
34
+ ## IDE & MCP Client Configuration
35
+
36
+ Add Sirius to your preferred environment:
37
+
38
+ ### Claude Code (`.mcp.json` or `~/.claude.json`)
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "sirius": {
43
+ "command": "npx",
44
+ "args": ["-y", "@sirius-mcp/mcp"]
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ ### Cursor (`.cursor/mcp.json` or Settings > Features > MCP)
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "sirius": {
55
+ "command": "npx",
56
+ "args": ["-y", "@sirius-mcp/mcp"]
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### VS Code (Cline / Roo Code)
63
+ In your Cline / Roo Code MCP settings (`cline_mcp_settings.json`):
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "sirius": {
68
+ "command": "npx",
69
+ "args": ["-y", "@sirius-mcp/mcp"]
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### Windsurf (`~/.codeium/windsurf/mcp_config.json`)
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "sirius": {
80
+ "command": "npx",
81
+ "args": ["-y", "@sirius-mcp/mcp"]
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### OpenCode (`opencode.json`)
88
+ ```json
89
+ {
90
+ "mcp": {
91
+ "sirius": {
92
+ "type": "local",
93
+ "command": ["npx", "-y", "@sirius-mcp/mcp"],
94
+ "enabled": true
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Docker Alternative
103
+
104
+ If you prefer running Sirius inside a container (for persistent servers or VPS deployments):
105
+
106
+ ```bash
107
+ docker run -i --rm \
108
+ --pull=always \
109
+ -p 127.0.0.1:4200:4200 \
110
+ -v "$PWD:/project" \
111
+ -e SIRIUS_PROJECT_ROOT=/project \
112
+ ghcr.io/sirius-mcp/sirius-mcp:latest
113
+ ```
114
+
115
+ ---
116
+
117
+ ## MCP Tools Provided
118
+
119
+ Once connected, your AI agent gains access to these primary context tools:
120
+
121
+ | Tool | Description |
122
+ | --- | --- |
123
+ | `context_list` | Lists all documents in the project memory (filterable by `type`, `status`, `path_prefix`). |
124
+ | `context_get` | Retrieves the full markdown content and frontmatter metadata of a specific document. |
125
+ | `context_search` | Performs full-text search (SQLite FTS5) over all indexed documents. |
126
+ | `context_upsert` | Creates or updates a context document (feature, decision, business-rule, architecture). |
127
+ | `context_crawl` | Crawls codebase or external documentation into context memory. |
128
+ | `reindex` | Rebuilds the local SQLite search index directly from the `.context/` markdown files. |
129
+ | `project_init` | Bootstraps a fresh `.context/` directory in a new repository. |
130
+
131
+ ---
132
+
133
+ ## MCP Resources Provided
134
+
135
+ - `context://document/{type}/{id}` — Direct URI to read any context document.
136
+ - `context://system/update-status` — Real-time update check and release metadata signal.
137
+
138
+ ---
139
+
140
+ ## Embedded Living Architecture Web Portal
141
+
142
+ Sirius includes a built-in web portal that visualizes your architecture blueprints, dependencies, decisions, and system rules in real-time.
143
+
144
+ When Sirius runs, navigate to:
145
+ ```
146
+ http://localhost:4200
147
+ ```
148
+ to explore your project's living knowledge base visually.
149
+
150
+ ---
151
+
152
+ ## Storage & Invariants
153
+
154
+ - **Markdown as Single Source of Truth**: Documents live in `.context/` inside your project git repository.
155
+ - **Auditable & Git-friendly**: Every decision or rule change can be committed, reviewed in PRs, and tracked in git history.
156
+ - **Ephemeral Index**: The local SQLite database (`.context/.index/sirius.db`) is completely disposable and gitignored; it can be rebuilt instantly with `reindex`.
157
+ - **Zero-network by default**: Solo tier runs completely local on your machine.
158
+
159
+ ---
160
+
161
+ ## Requirements
162
+
163
+ - **Node.js**: >= 22.0.0 (npm included)
164
+
165
+ ---
166
+
167
+ ## Documentation
168
+
169
+ Full documentation, guides, and deployment recipes are available at:
170
+ - Documentation: [siriusmcp.dev/docs/deploy](https://siriusmcp.dev/docs/deploy)
171
+ - GitHub Repository: [github.com/sirius-mcp/sirius-mcp](https://github.com/sirius-mcp/sirius-mcp)
172
+
173
+ ---
174
+
175
+ ## License
176
+
177
+ Proprietary. Free for solo and local developer use.
@@ -59,11 +59,11 @@ export class FtsIndexAdapter {
59
59
  if (!safeQuery) {
60
60
  return [];
61
61
  }
62
- const stmt = this.db.prepare(`SELECT id, type, status, title, path,
63
- snippet(documents_fts, 4, '<<<', '>>>', '...', 32) as snippet
64
- FROM documents_fts
65
- WHERE documents_fts MATCH ?
66
- ORDER BY rank
62
+ const stmt = this.db.prepare(`SELECT id, type, status, title, path,
63
+ snippet(documents_fts, 4, '<<<', '>>>', '...', 32) as snippet
64
+ FROM documents_fts
65
+ WHERE documents_fts MATCH ?
66
+ ORDER BY rank
67
67
  LIMIT ?`);
68
68
  let rows = stmt.all(safeQuery, limit);
69
69
  if (rows.length === 0) {
@@ -88,14 +88,14 @@ export class FtsIndexAdapter {
88
88
  }
89
89
  rebuild(docs) {
90
90
  this.ensureOpen();
91
- const createFts = this.db.prepare(`CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
92
- id UNINDEXED, type UNINDEXED, status UNINDEXED,
93
- title, body, path UNINDEXED,
94
- tokenize = 'porter unicode61'
91
+ const createFts = this.db.prepare(`CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
92
+ id UNINDEXED, type UNINDEXED, status UNINDEXED,
93
+ title, body, path UNINDEXED,
94
+ tokenize = 'porter unicode61'
95
95
  )`);
96
96
  const dropFts = this.db.prepare("DROP TABLE IF EXISTS documents_fts");
97
97
  const insertStmt = this.db.prepare("INSERT INTO documents_fts (id, type, status, title, body, path) VALUES (?, ?, ?, ?, ?, ?)");
98
- const setVersion = this.db.prepare(`INSERT INTO index_meta (key, value) VALUES ('schema_version', ?)
98
+ const setVersion = this.db.prepare(`INSERT INTO index_meta (key, value) VALUES ('schema_version', ?)
99
99
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`);
100
100
  const transaction = this.db.transaction((documents) => {
101
101
  dropFts.run();
@@ -110,14 +110,14 @@ export class FtsIndexAdapter {
110
110
  this._needsRebuild = false;
111
111
  }
112
112
  initializeSchema() {
113
- this.db.exec(`CREATE TABLE IF NOT EXISTS index_meta (
114
- key TEXT PRIMARY KEY,
115
- value TEXT NOT NULL
113
+ this.db.exec(`CREATE TABLE IF NOT EXISTS index_meta (
114
+ key TEXT PRIMARY KEY,
115
+ value TEXT NOT NULL
116
116
  )`);
117
- const createFts = this.db.prepare(`CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
118
- id UNINDEXED, type UNINDEXED, status UNINDEXED,
119
- title, body, path UNINDEXED,
120
- tokenize = 'porter unicode61'
117
+ const createFts = this.db.prepare(`CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
118
+ id UNINDEXED, type UNINDEXED, status UNINDEXED,
119
+ title, body, path UNINDEXED,
120
+ tokenize = 'porter unicode61'
121
121
  )`);
122
122
  createFts.run();
123
123
  const row = this.db
@@ -0,0 +1,4 @@
1
+ export declare function formatBackupTimestamp(date?: Date): string;
2
+ export declare function createBackup(filePath: string): Promise<string | null>;
3
+ export declare function atomicWrite(filePath: string, content: string): Promise<void>;
4
+ //# sourceMappingURL=backup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../../src/installer/backup.ts"],"names":[],"mappings":"AAIA,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CASrE;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkB3E;AAkCD,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BlF"}
@@ -0,0 +1,88 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ export function formatBackupTimestamp(date = new Date()) {
5
+ const pad = (n) => n.toString().padStart(2, "0");
6
+ const yyyy = date.getFullYear();
7
+ const mm = pad(date.getMonth() + 1);
8
+ const dd = pad(date.getDate());
9
+ const hh = pad(date.getHours());
10
+ const min = pad(date.getMinutes());
11
+ const ss = pad(date.getSeconds());
12
+ return `${yyyy}${mm}${dd}${hh}${min}${ss}`;
13
+ }
14
+ export async function createBackup(filePath) {
15
+ if (!fs.existsSync(filePath)) {
16
+ return null;
17
+ }
18
+ const parentDir = path.dirname(filePath);
19
+ const baseName = path.basename(filePath);
20
+ let timestamp = formatBackupTimestamp();
21
+ let backupPath = path.join(parentDir, `${baseName}.bak.${timestamp}`);
22
+ let counter = 1;
23
+ while (fs.existsSync(backupPath)) {
24
+ timestamp = `${formatBackupTimestamp()}_${counter++}`;
25
+ backupPath = path.join(parentDir, `${baseName}.bak.${timestamp}`);
26
+ }
27
+ await fs.promises.copyFile(filePath, backupPath);
28
+ return backupPath;
29
+ }
30
+ function sleep(ms) {
31
+ return new Promise((resolve) => setTimeout(resolve, ms));
32
+ }
33
+ async function writeAndSyncTemp(tempPath, content) {
34
+ const handle = await fs.promises.open(tempPath, "w");
35
+ try {
36
+ await handle.writeFile(content, "utf8");
37
+ await handle.sync();
38
+ }
39
+ finally {
40
+ await handle.close();
41
+ }
42
+ }
43
+ async function tryRenameWithBackoff(tempPath, targetPath) {
44
+ const backoffs = [100, 200, 400];
45
+ for (let i = 0; i <= backoffs.length; i++) {
46
+ try {
47
+ await fs.promises.rename(tempPath, targetPath);
48
+ return true;
49
+ }
50
+ catch (err) {
51
+ const code = err?.code;
52
+ const isLockError = code === "EBUSY" || code === "EPERM";
53
+ if (!isLockError || i === backoffs.length) {
54
+ break;
55
+ }
56
+ await sleep(backoffs[i]);
57
+ }
58
+ }
59
+ return false;
60
+ }
61
+ export async function atomicWrite(filePath, content) {
62
+ const parentDir = path.dirname(filePath);
63
+ await fs.promises.mkdir(parentDir, { recursive: true, mode: 0o700 });
64
+ const tempPath = path.join(parentDir, `${path.basename(filePath)}.tmp.${process.pid}.${crypto.randomUUID()}`);
65
+ try {
66
+ await writeAndSyncTemp(tempPath, content);
67
+ if (fs.existsSync(filePath)) {
68
+ const stat = await fs.promises.stat(filePath);
69
+ await fs.promises.chmod(tempPath, stat.mode);
70
+ }
71
+ const renamed = await tryRenameWithBackoff(tempPath, filePath);
72
+ if (!renamed) {
73
+ // Fallback: direct in-place buffered write
74
+ await fs.promises.writeFile(filePath, content, "utf8");
75
+ }
76
+ }
77
+ finally {
78
+ if (fs.existsSync(tempPath)) {
79
+ try {
80
+ await fs.promises.unlink(tempPath);
81
+ }
82
+ catch {
83
+ // cleanup best-effort
84
+ }
85
+ }
86
+ }
87
+ }
88
+ //# sourceMappingURL=backup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/installer/backup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,MAAM,UAAU,qBAAqB,CAAC,OAAa,IAAI,IAAI,EAAE;IAC3D,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IACpC,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACnC,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAClC,OAAO,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,SAAS,GAAG,qBAAqB,EAAE,CAAC;IACxC,IAAI,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,QAAQ,SAAS,EAAE,CAAC,CAAC;IAEtE,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,OAAO,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,SAAS,GAAG,GAAG,qBAAqB,EAAE,IAAI,OAAO,EAAE,EAAE,CAAC;QACtD,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,QAAQ,SAAS,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACjD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,QAAgB,EAAE,OAAe;IAC/D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACrD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACxC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,QAAgB,EAAE,UAAkB;IACtE,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,IAAI,GAAI,GAAyB,EAAE,IAAI,CAAC;YAC9C,MAAM,WAAW,GAAG,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,CAAC;YACzD,IAAI,CAAC,WAAW,IAAI,CAAC,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAC1C,MAAM;YACR,CAAC;YACD,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB,EAAE,OAAe;IACjE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAErE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CACxB,SAAS,EACT,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE,EAAE,CACvE,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9C,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,2CAA2C;YAC3C,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type InstallerOptions } from "./types.js";
2
+ export declare function parseInstallerArgs(args: string[]): InstallerOptions;
3
+ //# sourceMappingURL=cli-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-parser.d.ts","sourceRoot":"","sources":["../../src/installer/cli-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,YAAY,CAAC;AAkDpB,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,CA6BnE"}
@@ -0,0 +1,71 @@
1
+ import {} from "./types.js";
2
+ function parseValuedFlag(arg, val, options) {
3
+ if (arg === "--ide" || arg === "--clients") {
4
+ options.targets = val?.split(",").map((s) => s.trim().toLowerCase());
5
+ return true;
6
+ }
7
+ if (arg === "--category") {
8
+ options.category = val?.toLowerCase();
9
+ return true;
10
+ }
11
+ if (arg === "--scope") {
12
+ options.scope = val?.toLowerCase() ?? "global";
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+ function parseBooleanFlag(arg, options) {
18
+ if (arg === "--all") {
19
+ options.all = true;
20
+ return true;
21
+ }
22
+ if (arg === "--yes" || arg === "-y") {
23
+ options.yes = true;
24
+ return true;
25
+ }
26
+ if (arg === "--dry-run") {
27
+ options.dryRun = true;
28
+ return true;
29
+ }
30
+ if (arg === "--json") {
31
+ options.json = true;
32
+ return true;
33
+ }
34
+ if (arg === "--force") {
35
+ options.force = true;
36
+ return true;
37
+ }
38
+ if (arg === "--stdio") {
39
+ options.stdio = true;
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ export function parseInstallerArgs(args) {
45
+ const options = {
46
+ subcommand: "install",
47
+ scope: "global",
48
+ all: false,
49
+ yes: false,
50
+ dryRun: false,
51
+ json: false,
52
+ force: false,
53
+ stdio: false,
54
+ };
55
+ const subcommands = ["install", "doctor", "uninstall", "setup"];
56
+ for (let i = 0; i < args.length; i++) {
57
+ const arg = args[i];
58
+ if (i === 0 && subcommands.includes(arg)) {
59
+ options.subcommand = arg === "setup" ? "install" : arg;
60
+ continue;
61
+ }
62
+ if (parseValuedFlag(arg, args[i + 1], options)) {
63
+ i++;
64
+ }
65
+ else {
66
+ parseBooleanFlag(arg, options);
67
+ }
68
+ }
69
+ return options;
70
+ }
71
+ //# sourceMappingURL=cli-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-parser.js","sourceRoot":"","sources":["../../src/installer/cli-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type InstallerOptions, type PlatformEnv } from "./types.js";
2
+ export { parseInstallerArgs } from "./cli-parser.js";
3
+ export interface CliIo {
4
+ writeStdout: (data: string) => void;
5
+ writeStderr: (data: string) => void;
6
+ cwd?: string;
7
+ env?: PlatformEnv;
8
+ }
9
+ export declare function runInstallerCli(options: InstallerOptions, ioOverrides?: Partial<CliIo>): Promise<number>;
10
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/installer/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,WAAW,EAEjB,MAAM,YAAY,CAAC;AAOpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,KAAK;IACpB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAyJD,wBAAsB,eAAe,CACnC,OAAO,EAAE,gBAAgB,EACzB,WAAW,GAAE,OAAO,CAAC,KAAK,CAAM,GAC/B,OAAO,CAAC,MAAM,CAAC,CAkFjB"}
@@ -0,0 +1,208 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {} from "./types.js";
4
+ import { ALL_TARGETS, getTargetById, resolveTargetEnvironment } from "./targets.js";
5
+ import { detectAllTargets } from "./detector.js";
6
+ import { patchContent, removeContent } from "./patchers/index.js";
7
+ import { createBackup, atomicWrite } from "./backup.js";
8
+ import { generateUnifiedDiff, colorizeDiff } from "./diff.js";
9
+ export { parseInstallerArgs } from "./cli-parser.js";
10
+ const defaultIo = {
11
+ writeStdout: (d) => process.stdout.write(d),
12
+ writeStderr: (d) => process.stderr.write(d),
13
+ };
14
+ function resolveSelectedTargets(options) {
15
+ if (options.targets && options.targets.length > 0) {
16
+ return options.targets
17
+ .map((id) => getTargetById(id))
18
+ .filter((t) => t !== undefined);
19
+ }
20
+ if (options.category === "cli") {
21
+ return ALL_TARGETS.filter((t) => t.category === "cli");
22
+ }
23
+ if (options.category === "ides") {
24
+ return ALL_TARGETS.filter((t) => t.category === "ide");
25
+ }
26
+ return [...ALL_TARGETS];
27
+ }
28
+ function buildPatcherEnv(target, env) {
29
+ if (target.needsMacGuiEnvPatch && env.platform === "darwin") {
30
+ const nodeDir = path.dirname(env.execPath);
31
+ const defaults = ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"];
32
+ return { PATH: Array.from(new Set([nodeDir, ...defaults])).join(":") };
33
+ }
34
+ return undefined;
35
+ }
36
+ async function handleDryRun(target, paths, patched, raw, io) {
37
+ const diff = generateUnifiedDiff(raw, patched, path.basename(paths.configPath));
38
+ if (!io.writeStdout || io === defaultIo) {
39
+ io.writeStderr(`\n[dry-run] Proposed changes for ${target.displayName} (${paths.configPath}):\n`);
40
+ io.writeStderr(colorizeDiff(diff));
41
+ }
42
+ return {
43
+ targetId: target.id,
44
+ displayName: target.displayName,
45
+ category: target.category,
46
+ format: target.format,
47
+ targetPath: paths.configPath,
48
+ action: "CONFIGURED",
49
+ message: "Dry-run preview generated",
50
+ diff,
51
+ };
52
+ }
53
+ async function executeInstallTarget(target, options, env, io) {
54
+ const paths = target.resolvePaths(options.scope, env);
55
+ let raw = "";
56
+ if (fs.existsSync(paths.configPath)) {
57
+ raw = fs.readFileSync(paths.configPath, "utf8");
58
+ }
59
+ const patcherEnv = buildPatcherEnv(target, env);
60
+ const patched = patchContent(raw, {
61
+ targetId: target.id,
62
+ patcherType: target.patcherType,
63
+ format: target.format,
64
+ env: patcherEnv,
65
+ });
66
+ if (options.dryRun) {
67
+ return handleDryRun(target, paths, patched, raw, io);
68
+ }
69
+ let backupPath;
70
+ if (fs.existsSync(paths.configPath)) {
71
+ const bak = await createBackup(paths.configPath);
72
+ if (bak)
73
+ backupPath = bak;
74
+ }
75
+ await atomicWrite(paths.configPath, patched);
76
+ return {
77
+ targetId: target.id,
78
+ displayName: target.displayName,
79
+ category: target.category,
80
+ format: target.format,
81
+ targetPath: paths.configPath,
82
+ backupPath,
83
+ action: "CONFIGURED",
84
+ message: `Configured ${target.displayName}`,
85
+ };
86
+ }
87
+ async function executeUninstallTarget(target, options, env) {
88
+ const paths = target.resolvePaths(options.scope, env);
89
+ if (!fs.existsSync(paths.configPath)) {
90
+ return {
91
+ targetId: target.id,
92
+ displayName: target.displayName,
93
+ category: target.category,
94
+ format: target.format,
95
+ targetPath: paths.configPath,
96
+ action: "SKIPPED",
97
+ message: "Config file does not exist",
98
+ };
99
+ }
100
+ const raw = fs.readFileSync(paths.configPath, "utf8");
101
+ const uninstalled = removeContent(raw, {
102
+ targetId: target.id,
103
+ patcherType: target.patcherType,
104
+ format: target.format,
105
+ });
106
+ let backupPath;
107
+ const bak = await createBackup(paths.configPath);
108
+ if (bak)
109
+ backupPath = bak;
110
+ await atomicWrite(paths.configPath, uninstalled);
111
+ return {
112
+ targetId: target.id,
113
+ displayName: target.displayName,
114
+ category: target.category,
115
+ format: target.format,
116
+ targetPath: paths.configPath,
117
+ backupPath,
118
+ action: "REMOVED",
119
+ message: `Removed Sirius configuration from ${target.displayName}`,
120
+ };
121
+ }
122
+ function emitReport(report, options, io) {
123
+ if (options.json) {
124
+ io.writeStdout(JSON.stringify(report, null, 2) + "\n");
125
+ }
126
+ else {
127
+ io.writeStderr(`\n[sirius] Completed: ${report.summary.configured} configured, ${report.summary.skipped} skipped, ${report.summary.failed} failed.\n`);
128
+ }
129
+ }
130
+ export async function runInstallerCli(options, ioOverrides = {}) {
131
+ const io = { ...defaultIo, ...ioOverrides };
132
+ const env = io.env ?? {
133
+ ...resolveTargetEnvironment(),
134
+ ...(io.cwd ? { cwd: io.cwd } : {}),
135
+ };
136
+ if (options.subcommand === "doctor") {
137
+ const detected = await detectAllTargets(options.scope, env);
138
+ const results = detected.map((d) => ({
139
+ targetId: d.target.id,
140
+ displayName: d.target.displayName,
141
+ category: d.target.category,
142
+ format: d.target.format,
143
+ targetPath: d.configPath,
144
+ action: d.state === "ALREADY_CONFIGURED" ? "ALREADY_CONFIGURED" : "SKIPPED",
145
+ message: `State: ${d.state}`,
146
+ }));
147
+ const report = {
148
+ version: "1.4.1",
149
+ timestamp: new Date().toISOString(),
150
+ platform: env.platform,
151
+ arch: process.arch,
152
+ dryRun: false,
153
+ scope: options.scope,
154
+ summary: {
155
+ totalScanned: detected.length,
156
+ configured: detected.filter((d) => d.state === "ALREADY_CONFIGURED").length,
157
+ skipped: detected.filter((d) => d.state !== "ALREADY_CONFIGURED").length,
158
+ failed: 0,
159
+ },
160
+ targets: results,
161
+ };
162
+ emitReport(report, options, io);
163
+ return 0;
164
+ }
165
+ const selected = resolveSelectedTargets(options);
166
+ const results = [];
167
+ for (const target of selected) {
168
+ try {
169
+ if (options.subcommand === "uninstall") {
170
+ results.push(await executeUninstallTarget(target, options, env));
171
+ }
172
+ else {
173
+ results.push(await executeInstallTarget(target, options, env, io));
174
+ }
175
+ }
176
+ catch (err) {
177
+ results.push({
178
+ targetId: target.id,
179
+ displayName: target.displayName,
180
+ category: target.category,
181
+ format: target.format,
182
+ targetPath: target.resolvePaths(options.scope, env).configPath,
183
+ action: "FAILED",
184
+ message: err instanceof Error ? err.message : String(err),
185
+ });
186
+ }
187
+ }
188
+ const failedCount = results.filter((r) => r.action === "FAILED").length;
189
+ const configuredCount = results.filter((r) => r.action === "CONFIGURED" || r.action === "REMOVED").length;
190
+ const report = {
191
+ version: "1.4.1",
192
+ timestamp: new Date().toISOString(),
193
+ platform: env.platform,
194
+ arch: process.arch,
195
+ dryRun: options.dryRun,
196
+ scope: options.scope,
197
+ summary: {
198
+ totalScanned: selected.length,
199
+ configured: configuredCount,
200
+ skipped: selected.length - configuredCount - failedCount,
201
+ failed: failedCount,
202
+ },
203
+ targets: results,
204
+ };
205
+ emitReport(report, options, io);
206
+ return failedCount > 0 ? 1 : 0;
207
+ }
208
+ //# sourceMappingURL=cli.js.map