opencode-rules-md 0.6.5

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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +650 -0
  3. package/dist/cli.mjs +374 -0
  4. package/dist/src/active-rules-state.d.ts +12 -0
  5. package/dist/src/active-rules-state.d.ts.map +1 -0
  6. package/dist/src/active-rules-state.js +118 -0
  7. package/dist/src/active-rules-state.js.map +1 -0
  8. package/dist/src/cli/config.d.ts +62 -0
  9. package/dist/src/cli/config.d.ts.map +1 -0
  10. package/dist/src/cli/config.js +246 -0
  11. package/dist/src/cli/config.js.map +1 -0
  12. package/dist/src/cli/install.d.ts +21 -0
  13. package/dist/src/cli/install.d.ts.map +1 -0
  14. package/dist/src/cli/install.js +63 -0
  15. package/dist/src/cli/install.js.map +1 -0
  16. package/dist/src/cli/main.d.ts +19 -0
  17. package/dist/src/cli/main.d.ts.map +1 -0
  18. package/dist/src/cli/main.js +135 -0
  19. package/dist/src/cli/main.js.map +1 -0
  20. package/dist/src/cli/real-fs.d.ts +26 -0
  21. package/dist/src/cli/real-fs.d.ts.map +1 -0
  22. package/dist/src/cli/real-fs.js +42 -0
  23. package/dist/src/cli/real-fs.js.map +1 -0
  24. package/dist/src/cli/status.d.ts +19 -0
  25. package/dist/src/cli/status.d.ts.map +1 -0
  26. package/dist/src/cli/status.js +58 -0
  27. package/dist/src/cli/status.js.map +1 -0
  28. package/dist/src/debug.d.ts +3 -0
  29. package/dist/src/debug.d.ts.map +1 -0
  30. package/dist/src/debug.js +8 -0
  31. package/dist/src/debug.js.map +1 -0
  32. package/dist/src/git-branch.d.ts +2 -0
  33. package/dist/src/git-branch.d.ts.map +1 -0
  34. package/dist/src/git-branch.js +30 -0
  35. package/dist/src/git-branch.js.map +1 -0
  36. package/dist/src/index.d.ts +26 -0
  37. package/dist/src/index.d.ts.map +1 -0
  38. package/dist/src/index.js +55 -0
  39. package/dist/src/index.js.map +1 -0
  40. package/dist/src/mcp-tools.d.ts +8 -0
  41. package/dist/src/mcp-tools.d.ts.map +1 -0
  42. package/dist/src/mcp-tools.js +18 -0
  43. package/dist/src/mcp-tools.js.map +1 -0
  44. package/dist/src/message-context.d.ts +55 -0
  45. package/dist/src/message-context.d.ts.map +1 -0
  46. package/dist/src/message-context.js +110 -0
  47. package/dist/src/message-context.js.map +1 -0
  48. package/dist/src/message-paths.d.ts +34 -0
  49. package/dist/src/message-paths.d.ts.map +1 -0
  50. package/dist/src/message-paths.js +112 -0
  51. package/dist/src/message-paths.js.map +1 -0
  52. package/dist/src/project-fingerprint.d.ts +2 -0
  53. package/dist/src/project-fingerprint.d.ts.map +1 -0
  54. package/dist/src/project-fingerprint.js +61 -0
  55. package/dist/src/project-fingerprint.js.map +1 -0
  56. package/dist/src/rule-discovery.d.ts +49 -0
  57. package/dist/src/rule-discovery.d.ts.map +1 -0
  58. package/dist/src/rule-discovery.js +143 -0
  59. package/dist/src/rule-discovery.js.map +1 -0
  60. package/dist/src/rule-filter.d.ts +61 -0
  61. package/dist/src/rule-filter.d.ts.map +1 -0
  62. package/dist/src/rule-filter.js +180 -0
  63. package/dist/src/rule-filter.js.map +1 -0
  64. package/dist/src/rule-metadata.d.ts +29 -0
  65. package/dist/src/rule-metadata.d.ts.map +1 -0
  66. package/dist/src/rule-metadata.js +100 -0
  67. package/dist/src/rule-metadata.js.map +1 -0
  68. package/dist/src/runtime-chat.d.ts +34 -0
  69. package/dist/src/runtime-chat.d.ts.map +1 -0
  70. package/dist/src/runtime-chat.js +39 -0
  71. package/dist/src/runtime-chat.js.map +1 -0
  72. package/dist/src/runtime-context.d.ts +25 -0
  73. package/dist/src/runtime-context.d.ts.map +1 -0
  74. package/dist/src/runtime-context.js +109 -0
  75. package/dist/src/runtime-context.js.map +1 -0
  76. package/dist/src/runtime.d.ts +31 -0
  77. package/dist/src/runtime.d.ts.map +1 -0
  78. package/dist/src/runtime.js +238 -0
  79. package/dist/src/runtime.js.map +1 -0
  80. package/dist/src/session-store.d.ts +34 -0
  81. package/dist/src/session-store.d.ts.map +1 -0
  82. package/dist/src/session-store.js +90 -0
  83. package/dist/src/session-store.js.map +1 -0
  84. package/dist/src/utils.d.ts +15 -0
  85. package/dist/src/utils.d.ts.map +1 -0
  86. package/dist/src/utils.js +19 -0
  87. package/dist/src/utils.js.map +1 -0
  88. package/dist/tui/data/rules.d.ts +63 -0
  89. package/dist/tui/data/rules.d.ts.map +1 -0
  90. package/dist/tui/data/rules.js +173 -0
  91. package/dist/tui/data/rules.js.map +1 -0
  92. package/dist/tui/index.d.ts +8 -0
  93. package/dist/tui/index.d.ts.map +1 -0
  94. package/dist/tui/index.js +13 -0
  95. package/dist/tui/index.js.map +1 -0
  96. package/dist/tui/slots/sidebar-content.d.ts +11 -0
  97. package/dist/tui/slots/sidebar-content.d.ts.map +1 -0
  98. package/dist/tui/slots/sidebar-content.js +150 -0
  99. package/dist/tui/slots/sidebar-content.js.map +1 -0
  100. package/package.json +101 -0
  101. package/src/active-rules-state.ts +166 -0
  102. package/src/cli/config.ts +312 -0
  103. package/src/cli/install.ts +94 -0
  104. package/src/cli/main.ts +167 -0
  105. package/src/cli/real-fs.ts +65 -0
  106. package/src/cli/status.ts +80 -0
  107. package/src/debug.ts +9 -0
  108. package/src/git-branch.ts +37 -0
  109. package/src/index.ts +68 -0
  110. package/src/mcp-tools.ts +24 -0
  111. package/src/message-context.ts +139 -0
  112. package/src/message-paths.ts +153 -0
  113. package/src/project-fingerprint.ts +72 -0
  114. package/src/rule-discovery.ts +205 -0
  115. package/src/rule-filter.ts +269 -0
  116. package/src/rule-metadata.ts +161 -0
  117. package/src/runtime-chat.ts +68 -0
  118. package/src/runtime-context.ts +136 -0
  119. package/src/runtime.ts +369 -0
  120. package/src/session-store.ts +129 -0
  121. package/src/utils.ts +36 -0
  122. package/tui/.atl/.skill-registry.cache.json +3 -0
  123. package/tui/.atl/skill-registry.md +57 -0
  124. package/tui/data/rules.ts +230 -0
  125. package/tui/index.tsx +23 -0
  126. package/tui/slots/sidebar-content.tsx +364 -0
  127. package/tui/types/opencode-plugin-tui.d.ts +78 -0
@@ -0,0 +1,246 @@
1
+ /**
2
+ * CLI config helpers: discovery, JSONC-safe parsing, plugin normalization,
3
+ * backup rotation, and atomic write.
4
+ *
5
+ * All filesystem access goes through the CliFs interface so tests can inject
6
+ * an in-memory implementation without touching real disk.
7
+ */
8
+ import * as os from 'node:os';
9
+ import * as path from 'node:path';
10
+ // ---------------------------------------------------------------------------
11
+ // JSONC comment stripper
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * Strip JSONC comments and trailing commas while preserving
15
+ * comment-like sequences inside string literals.
16
+ */
17
+ export function stripJsoncComments(content) {
18
+ let result = '';
19
+ let i = 0;
20
+ while (i < content.length) {
21
+ const char = content[i];
22
+ // Start of string (double-quoted only — JSON only supports double quotes)?
23
+ if (char === '"') {
24
+ result += char;
25
+ i++;
26
+ while (i < content.length) {
27
+ const c = content[i];
28
+ if (c === '\\' && i + 1 < content.length) {
29
+ // Escape sequence — copy both chars verbatim
30
+ result += c + content[i + 1];
31
+ i += 2;
32
+ continue;
33
+ }
34
+ if (c === '"') {
35
+ result += c;
36
+ i++;
37
+ break;
38
+ }
39
+ result += c;
40
+ i++;
41
+ }
42
+ continue;
43
+ }
44
+ // Line comment?
45
+ if (char === '/' && content[i + 1] === '/') {
46
+ while (i < content.length && content[i] !== '\n') {
47
+ i++;
48
+ }
49
+ continue;
50
+ }
51
+ // Block comment?
52
+ if (char === '/' && content[i + 1] === '*') {
53
+ i += 2;
54
+ while (i + 1 < content.length && !(content[i] === '*' && content[i + 1] === '/')) {
55
+ i++;
56
+ }
57
+ // Only skip past */ if we actually found it
58
+ if (i + 1 < content.length) {
59
+ i += 2; // skip */
60
+ }
61
+ continue;
62
+ }
63
+ // Trailing comma before } or ]
64
+ if (char === ',' &&
65
+ (content[i + 1] === '}' || content[i + 1] === ']')) {
66
+ i++;
67
+ continue;
68
+ }
69
+ result += char;
70
+ i++;
71
+ }
72
+ return result;
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // Config path resolution
76
+ // ---------------------------------------------------------------------------
77
+ /**
78
+ * Resolve the global opencode config path using the same precedence as
79
+ * opencode itself: OPENCODE_CONFIG_DIR → XDG_CONFIG_HOME → HOME fallback.
80
+ *
81
+ * On Windows, APPDATA is tried as a last resort when XDG_CONFIG_HOME and
82
+ * HOME are both absent.
83
+ */
84
+ export function resolveGlobalConfigPath(fs, opts = {}) {
85
+ const dir = resolveConfigDir();
86
+ if (opts.ensureDir && !fs.existsSync(dir)) {
87
+ fs.mkdirSync(dir, { recursive: true });
88
+ }
89
+ return path.join(dir, 'opencode.json');
90
+ }
91
+ function resolveConfigDir() {
92
+ if (process.env.OPENCODE_CONFIG_DIR) {
93
+ return process.env.OPENCODE_CONFIG_DIR;
94
+ }
95
+ if (process.env.XDG_CONFIG_HOME) {
96
+ return path.join(process.env.XDG_CONFIG_HOME, 'opencode');
97
+ }
98
+ const home = process.env.HOME || os.homedir();
99
+ if (home) {
100
+ return path.join(home, '.config', 'opencode');
101
+ }
102
+ // Windows fallback
103
+ const appdata = process.env.APPDATA;
104
+ if (appdata) {
105
+ return path.join(appdata, 'opencode');
106
+ }
107
+ throw new Error('Cannot resolve config dir: none of OPENCODE_CONFIG_DIR, XDG_CONFIG_HOME, HOME, or APPDATA is set');
108
+ }
109
+ // ---------------------------------------------------------------------------
110
+ // JSONC-safe config load
111
+ // ---------------------------------------------------------------------------
112
+ /**
113
+ * Load and parse the global opencode config.
114
+ * Treats a missing file as an empty config object (not an error).
115
+ * Surfaces parse errors so callers can exit non-zero without corrupting data.
116
+ */
117
+ export function loadGlobalConfig(fs) {
118
+ const configPath = resolveGlobalConfigPath(fs);
119
+ if (!fs.existsSync(configPath)) {
120
+ return { path: configPath, existed: false, config: {} };
121
+ }
122
+ try {
123
+ const raw = fs.readFileSync(configPath);
124
+ const stripped = stripJsoncComments(raw);
125
+ // Empty after stripping means comment-only file — treat as {}
126
+ if (stripped.trim() === '') {
127
+ return { path: configPath, existed: true, config: {} };
128
+ }
129
+ const parsed = JSON.parse(stripped);
130
+ return { path: configPath, existed: true, config: parsed };
131
+ }
132
+ catch (err) {
133
+ const error = err instanceof Error ? err : new Error(String(err));
134
+ return { path: configPath, existed: true, config: {}, parseError: error };
135
+ }
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // Plugin normalization helpers
139
+ // ---------------------------------------------------------------------------
140
+ /**
141
+ * Return the plugin array with opencode-rules-md* entries deduplicated by
142
+ * name prefix, keeping the last occurrence of each unique prefix.
143
+ */
144
+ export function normalizePlugin(config) {
145
+ const raw = Array.isArray(config['plugin']) ? config['plugin'] : [];
146
+ const seen = new Map(); // prefix → last index
147
+ const result = [];
148
+ for (const item of raw) {
149
+ if (typeof item !== 'string')
150
+ continue;
151
+ const prefix = getPluginPrefix(item);
152
+ seen.set(prefix, result.length);
153
+ result.push(item);
154
+ }
155
+ // Collapse entries sharing a prefix to only the last one
156
+ return collapseByPrefix(result, seen);
157
+ }
158
+ /**
159
+ * Remove all plugins matching the given prefix (used to dedupe before append).
160
+ */
161
+ export function removePlugin(config, prefix) {
162
+ if (!Array.isArray(config['plugin']))
163
+ return;
164
+ const raw = config['plugin'];
165
+ config['plugin'] = raw.filter(item => typeof item === 'string' && !item.startsWith(prefix));
166
+ }
167
+ /**
168
+ * Add a plugin specifier, removing any existing entry with the same prefix first
169
+ * so it ends up at the specified position (default: end). This makes the operation
170
+ * idempotent — re-adding the same specifier just moves it to the end.
171
+ */
172
+ export function addPlugin(config, specifier) {
173
+ if (!Array.isArray(config['plugin'])) {
174
+ config['plugin'] = [];
175
+ }
176
+ const plugin = config['plugin'];
177
+ const prefix = getPluginPrefix(specifier);
178
+ // Remove any existing entry with the same prefix
179
+ const removed = plugin.filter(p => getPluginPrefix(p) !== prefix);
180
+ removed.push(specifier);
181
+ config['plugin'] = removed;
182
+ }
183
+ // ---------------------------------------------------------------------------
184
+ // Backup rotation
185
+ // ---------------------------------------------------------------------------
186
+ /**
187
+ * Write a timestamped backup of the given config path and rotate so at most
188
+ * 3 backups are retained (oldest deleted first).
189
+ * Returns the path of the newly created backup.
190
+ */
191
+ export function rotateBackups(fs, configPath, _pluginName) {
192
+ const dir = path.dirname(configPath);
193
+ const base = path.basename(configPath);
194
+ const timestamp = Date.now();
195
+ const newBak = path.join(dir, `${base}.bak.${timestamp}`);
196
+ if (fs.existsSync(configPath)) {
197
+ fs.copyFileSync(configPath, newBak);
198
+ }
199
+ else {
200
+ fs.writeFileSync(newBak, '', 'utf-8');
201
+ }
202
+ // Collect existing backups sorted by timestamp (lexical sort on numeric suffix)
203
+ const entries = fs.readdirSync(dir);
204
+ const bakFiles = entries
205
+ .filter(e => e.startsWith(base + '.bak.'))
206
+ .sort();
207
+ // Remove oldest until only 3 remain
208
+ while (bakFiles.length > 3) {
209
+ const oldest = bakFiles.shift();
210
+ fs.unlinkSync(path.join(dir, oldest));
211
+ }
212
+ return newBak;
213
+ }
214
+ // ---------------------------------------------------------------------------
215
+ // Atomic write
216
+ // ---------------------------------------------------------------------------
217
+ /**
218
+ * Write content to a temporary sibling file then rename it into place.
219
+ * The final path is never in a partially-written state.
220
+ */
221
+ export function writeAtomically(fs, configPath, content) {
222
+ const dir = path.dirname(configPath);
223
+ const base = path.basename(configPath);
224
+ const tmpPath = path.join(dir, `.${base}.tmp.${Date.now()}`);
225
+ fs.writeFileSync(tmpPath, content, 'utf-8');
226
+ fs.renameSync(tmpPath, configPath);
227
+ }
228
+ // ---------------------------------------------------------------------------
229
+ // Internal helpers
230
+ // ---------------------------------------------------------------------------
231
+ /**
232
+ * Extract the name part of a plugin specifier, stripping any version suffix.
233
+ * e.g. "opencode-rules-md@0.1.0" → "opencode-rules-md"
234
+ */
235
+ function getPluginPrefix(specifier) {
236
+ const atIdx = specifier.indexOf('@');
237
+ return atIdx === -1 ? specifier : specifier.substring(0, atIdx);
238
+ }
239
+ /**
240
+ * Collapse plugins by prefix, keeping only the last occurrence of each.
241
+ */
242
+ function collapseByPrefix(plugins, lastSeen) {
243
+ const keptIndices = new Set([...lastSeen.values()]);
244
+ return plugins.filter((_, idx) => keptIndices.has(idx));
245
+ }
246
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/cli/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * install command: idempotently append opencode-rules-md to the global config.
3
+ *
4
+ * Pipeline:
5
+ * loadGlobalConfig → abort if parseError → dedupe → append specifier
6
+ * → check no-op → (dry-run? print & return "planned")
7
+ * → rotateBackups → writeAtomically → print & return "wrote"
8
+ */
9
+ import type { CliFs } from './real-fs.js';
10
+ export interface InstallResult {
11
+ status: 'wrote' | 'planned' | 'noop' | 'error';
12
+ path: string;
13
+ specifier: string;
14
+ backup?: string;
15
+ parseError?: Error;
16
+ }
17
+ export declare function runInstall(opts: {
18
+ version?: string;
19
+ dryRun?: boolean;
20
+ }, fs?: CliFs): InstallResult;
21
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/cli/install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAO1C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;AAMD,wBAAgB,UAAU,CACxB,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5C,EAAE,GAAE,KAAc,GACjB,aAAa,CAqDf"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * install command: idempotently append opencode-rules-md to the global config.
3
+ *
4
+ * Pipeline:
5
+ * loadGlobalConfig → abort if parseError → dedupe → append specifier
6
+ * → check no-op → (dry-run? print & return "planned")
7
+ * → rotateBackups → writeAtomically → print & return "wrote"
8
+ */
9
+ import { loadGlobalConfig, normalizePlugin, removePlugin, addPlugin, rotateBackups, writeAtomically } from './config.js';
10
+ import { realFs } from './real-fs.js';
11
+ // ---------------------------------------------------------------------------
12
+ // Run install
13
+ // ---------------------------------------------------------------------------
14
+ export function runInstall(opts, fs = realFs) {
15
+ // Load config
16
+ const loadResult = loadGlobalConfig(fs);
17
+ // Abort on parse error — do not corrupt malformed config
18
+ if (loadResult.parseError) {
19
+ return {
20
+ status: 'error',
21
+ path: loadResult.path,
22
+ specifier: buildSpecifier(opts.version),
23
+ parseError: loadResult.parseError,
24
+ };
25
+ }
26
+ const configPath = loadResult.path;
27
+ const config = loadResult.config;
28
+ // Normalize plugin array (dedupe by prefix)
29
+ config['plugin'] = normalizePlugin(config);
30
+ // Build the specifier
31
+ const specifier = buildSpecifier(opts.version);
32
+ // Check if already installed with the same specifier — no-op
33
+ const existing = config['plugin'] ?? [];
34
+ if (existing.includes(specifier)) {
35
+ return { status: 'noop', path: configPath, specifier };
36
+ }
37
+ // Remove any existing opencode-rules-md* entries so this specifier is deduplicated
38
+ removePlugin(config, 'opencode-rules-md');
39
+ // Add the new (or re-added) specifier at the end
40
+ addPlugin(config, specifier);
41
+ // Dry-run: serialize and print planned content, make no changes
42
+ if (opts.dryRun) {
43
+ const planned = JSON.stringify(config, null, 2);
44
+ console.log('Planned config:\n' + planned);
45
+ return { status: 'planned', path: configPath, specifier };
46
+ }
47
+ // Write: rotate backups first, then atomic write
48
+ const backup = rotateBackups(fs, configPath, 'opencode-rules-md');
49
+ const serialized = JSON.stringify(config, null, 2);
50
+ writeAtomically(fs, configPath, serialized);
51
+ console.log(`Installed ${specifier} to ${configPath}`);
52
+ if (backup) {
53
+ console.log(`Backup written to ${backup}`);
54
+ }
55
+ return { status: 'wrote', path: configPath, specifier, backup };
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Internal helpers
59
+ // ---------------------------------------------------------------------------
60
+ function buildSpecifier(version) {
61
+ return version ? `opencode-rules-md@${version}` : 'opencode-rules-md';
62
+ }
63
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/cli/install.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEzH,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AActC,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,MAAM,UAAU,UAAU,CACxB,IAA4C,EAC5C,KAAY,MAAM;IAElB,cAAc;IACd,MAAM,UAAU,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC;IAExC,yDAAyD;IACzD,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;QAC1B,OAAO;YACL,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,SAAS,EAAE,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;YACvC,UAAU,EAAE,UAAU,CAAC,UAAU;SAClC,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC;IACnC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAEjC,4CAA4C;IAC5C,MAAM,CAAC,QAAQ,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAE3C,sBAAsB;IACtB,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAE/C,6DAA6D;IAC7D,MAAM,QAAQ,GAAI,MAAM,CAAC,QAAQ,CAA0B,IAAI,EAAE,CAAC;IAClE,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IACzD,CAAC;IAED,mFAAmF;IACnF,YAAY,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAE1C,iDAAiD;IACjD,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAE7B,gEAAgE;IAChE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,OAAO,CAAC,CAAC;QAC3C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IAC5D,CAAC;IAED,iDAAiD;IACjD,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;IAClE,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnD,eAAe,CAAC,EAAE,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IAE5C,OAAO,CAAC,GAAG,CAAC,aAAa,SAAS,OAAO,UAAU,EAAE,CAAC,CAAC;IACvD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,qBAAqB,MAAM,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAClE,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,SAAS,cAAc,CAAC,OAAgB;IACtC,OAAO,OAAO,CAAC,CAAC,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC;AACxE,CAAC"}
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entrypoint for opencode-rules-md installer.
4
+ *
5
+ * Supports:
6
+ * install — add opencode-rules-md to the global opencode config
7
+ * status — report whether opencode-rules-md is installed
8
+ *
9
+ * Flags:
10
+ * --version <v> — specify a plugin version to install
11
+ * --latest — install the latest version (default)
12
+ * --dry-run — show what would be written, without changing files
13
+ * --yes — skip confirmation prompts (future use)
14
+ * -h, --help — show this help text
15
+ */
16
+ import type { CliFs } from './real-fs.js';
17
+ export type ExitCode = 0 | 1 | 2;
18
+ export declare function runMain(argv: string[], fs?: CliFs): Promise<ExitCode>;
19
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../src/cli/main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA6E1C,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEjC,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0DnF"}
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entrypoint for opencode-rules-md installer.
4
+ *
5
+ * Supports:
6
+ * install — add opencode-rules-md to the global opencode config
7
+ * status — report whether opencode-rules-md is installed
8
+ *
9
+ * Flags:
10
+ * --version <v> — specify a plugin version to install
11
+ * --latest — install the latest version (default)
12
+ * --dry-run — show what would be written, without changing files
13
+ * --yes — skip confirmation prompts (future use)
14
+ * -h, --help — show this help text
15
+ */
16
+ import { parseArgs } from 'node:util';
17
+ import { runInstall } from './install.js';
18
+ import { runStatus } from './status.js';
19
+ import { realFs } from './real-fs.js';
20
+ // ---------------------------------------------------------------------------
21
+ // Help text
22
+ // ---------------------------------------------------------------------------
23
+ const HELP_TEXT = `opencode-rules-md CLI
24
+
25
+ Usage: opencode-rules-md <command> [options]
26
+
27
+ Commands:
28
+ install Add opencode-rules-md to the global opencode config
29
+ status Report whether opencode-rules-md is installed
30
+
31
+ Options:
32
+ --version <v> Specify a plugin version to install
33
+ --latest Install the latest version (default)
34
+ --dry-run Show what would be written, without changing files
35
+ --yes Skip confirmation prompts (future use)
36
+ -h, --help Show this help text
37
+ `.trim();
38
+ const USAGE_ERROR_TEXT = `Error: unknown command. Run 'opencode-rules-md --help' for usage.
39
+ `.trim();
40
+ function parseCliArgs(argv) {
41
+ const { positionals, values } = parseArgs({
42
+ args: argv,
43
+ allowPositionals: true,
44
+ options: {
45
+ version: { type: 'string' },
46
+ latest: { type: 'boolean' },
47
+ 'dry-run': { type: 'boolean' },
48
+ yes: { type: 'boolean' },
49
+ h: { type: 'boolean' },
50
+ help: { type: 'boolean' },
51
+ },
52
+ });
53
+ // Extract command (first positional)
54
+ const command = positionals[0] ?? null;
55
+ // Extract options from parsed values
56
+ // Note: -h sets key 'h', --help sets key 'help'
57
+ const options = {
58
+ ...(values.version !== undefined && { version: values.version }),
59
+ ...(values.latest && { latest: true }),
60
+ ...(values['dry-run'] && { dryRun: true }),
61
+ ...(values.yes && { yes: true }),
62
+ ...((values.help || values.h) && { help: true }),
63
+ };
64
+ // Detect unknown flags (tokens that look like flags but aren't recognized)
65
+ // parseArgs already filters known tokens, so we check if any positional
66
+ // looks like an unknown option
67
+ const unknownFlags = [];
68
+ return { command, options, unknownFlags };
69
+ }
70
+ export async function runMain(argv, fs = realFs) {
71
+ try {
72
+ const { command, options, unknownFlags } = parseCliArgs(argv);
73
+ // Handle help flag at top level (before command dispatch)
74
+ if (options.help) {
75
+ console.log(HELP_TEXT);
76
+ return 0;
77
+ }
78
+ // Handle unknown flags
79
+ if (unknownFlags.length > 0) {
80
+ console.error(`Error: unknown option(s): ${unknownFlags.join(', ')}`);
81
+ console.error(USAGE_ERROR_TEXT);
82
+ return 2;
83
+ }
84
+ // No command given
85
+ if (!command) {
86
+ console.error(USAGE_ERROR_TEXT);
87
+ return 2;
88
+ }
89
+ switch (command) {
90
+ case 'install': {
91
+ const installOpts = {};
92
+ if (options.version !== undefined)
93
+ installOpts.version = options.version;
94
+ if (options.dryRun)
95
+ installOpts.dryRun = true;
96
+ const installResult = runInstall(installOpts, fs);
97
+ // Map install status to exit code
98
+ if (installResult.status === 'error') {
99
+ return 1;
100
+ }
101
+ return 0;
102
+ }
103
+ case 'status': {
104
+ const statusResult = runStatus(fs);
105
+ // Print status to stdout
106
+ if (statusResult.installed) {
107
+ console.log(`opencode-rules-md is installed (${statusResult.specifier})`);
108
+ }
109
+ else {
110
+ console.log('opencode-rules-md is not installed');
111
+ }
112
+ // Exit code 0 regardless of install state (status is read-only)
113
+ return 0;
114
+ }
115
+ default: {
116
+ console.error(`Error: unknown command '${command}'`);
117
+ console.error(USAGE_ERROR_TEXT);
118
+ return 2;
119
+ }
120
+ }
121
+ }
122
+ catch (err) {
123
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
124
+ return 1;
125
+ }
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // Entry point
129
+ // ---------------------------------------------------------------------------
130
+ // Only run if executed directly (not imported as a module)
131
+ if (import.meta.url === `file://${process.argv[1]}`) {
132
+ const exitCode = await runMain(process.argv.slice(2));
133
+ process.exit(exitCode);
134
+ }
135
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../../src/cli/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Thin synchronous adapter over node:fs that implements the CliFs interface.
3
+ * All production CLI disk I/O flows through this module — no direct node:fs
4
+ * calls outside this file.
5
+ */
6
+ /**
7
+ * Synchronous filesystem interface used by the CLI.
8
+ * Tests supply an in-memory implementation; production uses this adapter.
9
+ */
10
+ export interface CliFs {
11
+ readFileSync(path: string): string;
12
+ writeFileSync(path: string, content: string, encoding?: string): void;
13
+ renameSync(from: string, to: string): void;
14
+ copyFileSync(from: string, to: string): void;
15
+ unlinkSync(path: string): void;
16
+ mkdirSync(path: string, opts?: {
17
+ recursive?: boolean;
18
+ }): void;
19
+ readdirSync(path: string): string[];
20
+ existsSync(path: string): boolean;
21
+ }
22
+ /**
23
+ * Production CliFs implementation backed by node:fs (synchronous).
24
+ */
25
+ export declare const realFs: CliFs;
26
+ //# sourceMappingURL=real-fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"real-fs.d.ts","sourceRoot":"","sources":["../../../src/cli/real-fs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;;GAGG;AACH,MAAM,WAAW,KAAK;IACpB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACpC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED;;GAEG;AACH,eAAO,MAAM,MAAM,EAAE,KAqCpB,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Thin synchronous adapter over node:fs that implements the CliFs interface.
3
+ * All production CLI disk I/O flows through this module — no direct node:fs
4
+ * calls outside this file.
5
+ */
6
+ import * as fs from 'node:fs';
7
+ import * as nodePath from 'node:path';
8
+ /**
9
+ * Production CliFs implementation backed by node:fs (synchronous).
10
+ */
11
+ export const realFs = {
12
+ readFileSync(filePath) {
13
+ return fs.readFileSync(filePath, 'utf-8');
14
+ },
15
+ writeFileSync(filePath, content, _encoding) {
16
+ // Ensure parent directory exists before writing
17
+ const dir = nodePath.dirname(filePath);
18
+ if (!fs.existsSync(dir)) {
19
+ fs.mkdirSync(dir, { recursive: true });
20
+ }
21
+ fs.writeFileSync(filePath, content, 'utf-8');
22
+ },
23
+ renameSync(from, to) {
24
+ fs.renameSync(from, to);
25
+ },
26
+ copyFileSync(from, to) {
27
+ fs.copyFileSync(from, to);
28
+ },
29
+ unlinkSync(filePath) {
30
+ fs.unlinkSync(filePath);
31
+ },
32
+ mkdirSync(filePath, opts) {
33
+ fs.mkdirSync(filePath, opts);
34
+ },
35
+ readdirSync(filePath) {
36
+ return fs.readdirSync(filePath);
37
+ },
38
+ existsSync(filePath) {
39
+ return fs.existsSync(filePath);
40
+ },
41
+ };
42
+ //# sourceMappingURL=real-fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"real-fs.js","sourceRoot":"","sources":["../../../src/cli/real-fs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAC;AAiBtC;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAU;IAC3B,YAAY,CAAC,QAAgB;QAC3B,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,aAAa,CAAC,QAAgB,EAAE,OAAe,EAAE,SAAkB;QACjE,gDAAgD;QAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;QACD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,UAAU,CAAC,IAAY,EAAE,EAAU;QACjC,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,YAAY,CAAC,IAAY,EAAE,EAAU;QACnC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,UAAU,CAAC,QAAgB;QACzB,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED,SAAS,CAAC,QAAgB,EAAE,IAA8B;QACxD,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,WAAW,CAAC,QAAgB;QAC1B,OAAO,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAED,UAAU,CAAC,QAAgB;QACzB,OAAO,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;CACF,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * status command: read-only probe that reports install state.
3
+ *
4
+ * Reports:
5
+ * - installed: yes/no
6
+ * - specifier: the registered opencode-rules-md entry (or undefined)
7
+ * - path: config file path
8
+ * - version: bundled CLI version from package.json
9
+ */
10
+ import type { CliFs } from './real-fs.js';
11
+ export interface StatusResult {
12
+ installed: boolean;
13
+ path: string;
14
+ specifier?: string;
15
+ version: string;
16
+ parseError?: Error;
17
+ }
18
+ export declare function runStatus(fs?: CliFs): StatusResult;
19
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/cli/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAS1C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,KAAK,CAAC;CACpB;AAMD,wBAAgB,SAAS,CAAC,EAAE,GAAE,KAAc,GAAG,YAAY,CA+B1D"}