convene-cli 1.0.3 → 1.0.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.
@@ -34,13 +34,15 @@ function upsertMarkerBlock(content, block) {
34
34
  const sep = content.length === 0 ? '' : content.endsWith('\n') ? '\n' : '\n\n';
35
35
  return content + sep + block + '\n';
36
36
  }
37
- function writeIfChanged(file, content, backup = true) {
37
+ // Every file this writes lives inside the git repo, so git history IS the backup —
38
+ // dropping a sibling `.bak` just litters the working tree (and shows up as untracked
39
+ // noise / risks being committed). The only `.bak` we keep is for the user's GLOBAL
40
+ // ~/.claude/settings.json, which is outside any repo (see registerHook).
41
+ function writeIfChanged(file, content) {
38
42
  const exists = node_fs_1.default.existsSync(file);
39
43
  const old = exists ? node_fs_1.default.readFileSync(file, 'utf8') : null;
40
44
  if (old === content)
41
45
  return 'unchanged';
42
- if (exists && backup)
43
- node_fs_1.default.writeFileSync(file + '.bak', old);
44
46
  node_fs_1.default.writeFileSync(file, content);
45
47
  return exists ? 'updated' : 'created';
46
48
  }
@@ -81,8 +83,7 @@ function ensureGitignoreGuard(top) {
81
83
  next = next + sep + guard;
82
84
  }
83
85
  if (next !== old) {
84
- if (node_fs_1.default.existsSync(file))
85
- node_fs_1.default.writeFileSync(file + '.bak', old);
86
+ // .gitignore is git-tracked — no .bak (git history is the backup).
86
87
  node_fs_1.default.writeFileSync(file, next);
87
88
  }
88
89
  // Belt-and-suspenders: confirm the join-token-bearing file is actually trackable.
@@ -174,6 +175,66 @@ function seedMemory(top, slug, baseUrl) {
174
175
  /* memory seed is best-effort */
175
176
  }
176
177
  }
178
+ // ── Cross-agent rule files ───────────────────────────────────────────────────
179
+ // AGENTS.md (written above) is the keystone — Codex, Windsurf, Cursor, and Cline
180
+ // auto-read it. These add each agent's NATIVE rule file so they reliably pick
181
+ // Convene up and call the `convene` CLI themselves. No per-prompt FRESH injection
182
+ // outside Claude Code (and, later, Codex) — see CONVENE_PROTOCOL.md.
183
+ function writeCursorRule(top, block) {
184
+ const file = node_path_1.default.join(top, '.cursor', 'rules', 'convene.mdc');
185
+ const frontmatter = '---\ndescription: Convene AI coordination bus — read channel context and post updates\nalwaysApply: true\n---\n\n';
186
+ const old = node_fs_1.default.existsSync(file) ? node_fs_1.default.readFileSync(file, 'utf8') : null;
187
+ const content = old !== null ? upsertMarkerBlock(old, block) : frontmatter + block + '\n';
188
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
189
+ const r = writeIfChanged(file, content);
190
+ log(`${r === 'unchanged' ? '·' : '✓'} .cursor/rules/convene.mdc (${r}) — Cursor auto-applies it every chat`);
191
+ }
192
+ function writeClineRule(top, block) {
193
+ const file = node_path_1.default.join(top, '.clinerules', 'convene.md');
194
+ const old = node_fs_1.default.existsSync(file) ? node_fs_1.default.readFileSync(file, 'utf8') : '';
195
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
196
+ const r = writeIfChanged(file, upsertMarkerBlock(old, block));
197
+ log(`${r === 'unchanged' ? '·' : '✓'} .clinerules/convene.md (${r}) — Cline workspace rule`);
198
+ }
199
+ function writeGeminiSettings(top) {
200
+ const file = node_path_1.default.join(top, '.gemini', 'settings.json');
201
+ let obj = {};
202
+ if (node_fs_1.default.existsSync(file)) {
203
+ try {
204
+ obj = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8')) || {};
205
+ }
206
+ catch {
207
+ log('· .gemini/settings.json (left as-is — unparseable JSON)');
208
+ return;
209
+ }
210
+ }
211
+ obj.context = obj.context && typeof obj.context === 'object' ? obj.context : {};
212
+ const cur = obj.context.fileName;
213
+ let names = Array.isArray(cur) ? cur : typeof cur === 'string' ? [cur] : [];
214
+ if (!names.includes('AGENTS.md'))
215
+ names = [...names, 'AGENTS.md'];
216
+ obj.context.fileName = names;
217
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
218
+ const r = writeIfChanged(file, JSON.stringify(obj, null, 2) + '\n');
219
+ log(`${r === 'unchanged' ? '·' : '✓'} .gemini/settings.json (${r}) — Gemini CLI reads AGENTS.md each prompt`);
220
+ }
221
+ function writeAiderConf(top) {
222
+ // No YAML parser bundled, so write-if-absent (don't risk clobbering an existing config).
223
+ const file = node_path_1.default.join(top, '.aider.conf.yml');
224
+ if (node_fs_1.default.existsSync(file)) {
225
+ log('· .aider.conf.yml (exists) — add `read: [AGENTS.md, CONVENE_PROTOCOL.md]` so Aider loads Convene');
226
+ return;
227
+ }
228
+ node_fs_1.default.writeFileSync(file, 'read:\n - AGENTS.md\n - CONVENE_PROTOCOL.md\n');
229
+ log('✓ .aider.conf.yml (created) — Aider loads the Convene instructions at startup');
230
+ }
231
+ function writeAgentRules(top, slug, member, baseUrl) {
232
+ const block = (0, protocol_1.conveneBlock)(slug, member, baseUrl);
233
+ writeCursorRule(top, block);
234
+ writeClineRule(top, block);
235
+ writeGeminiSettings(top);
236
+ writeAiderConf(top);
237
+ }
177
238
  async function init(opts) {
178
239
  const top = (0, git_1.gitToplevel)();
179
240
  if (!top)
@@ -185,6 +246,7 @@ async function init(opts) {
185
246
  const skipHook = opts.noHook === true || opts.hook === false;
186
247
  const skipGithook = opts.noGithook === true || opts.githook === false;
187
248
  const skipJoinToken = opts.noJoinToken === true || opts.joinToken === false;
249
+ const skipAgentRules = opts.noAgentRules === true || opts.agentRules === false;
188
250
  let slug = opts.slug || existing?.slug;
189
251
  let displayName = opts.name || existing?.displayName;
190
252
  let joinToken = existing?.joinToken; // reuse if present (keeps init idempotent)
@@ -321,6 +383,14 @@ async function init(opts) {
321
383
  // 7b. committed git pre-push hook — the tool-agnostic backstop that auto-posts a
322
384
  // [STATUS] when work is pushed (fires for Codex/Cowork/humans, not just Claude).
323
385
  installGitHookStep(top, skipGithook);
386
+ // 7c. Cross-agent rule files so Codex/Cursor/Cline/Gemini/Aider pick Convene up too
387
+ // (AGENTS.md already covers the auto-readers; these add each agent's native file).
388
+ if (skipAgentRules) {
389
+ log('Skipped cross-agent rule files (--no-agent-rules).');
390
+ }
391
+ else {
392
+ writeAgentRules(top, slug, member, baseUrl);
393
+ }
324
394
  // 8. memory seed (best-effort, outside the repo)
325
395
  seedMemory(top, slug, baseUrl);
326
396
  // 9. teammate one-liner
package/dist/index.js CHANGED
@@ -124,6 +124,7 @@ program
124
124
  .option('--no-hook', 'do not register the UserPromptSubmit hook')
125
125
  .option('--no-githook', 'do not install the git pre-push auto-status hook')
126
126
  .option('--no-join-token', 'do not mint/commit a self-serve join token (use for public repos)')
127
+ .option('--no-agent-rules', 'do not write Cursor/Cline/Gemini/Aider rule files (Claude/Codex via AGENTS.md still work)')
127
128
  .option('--force', 'commit a join token even if the repo looks public (overrides the guard)')
128
129
  .option('--yes', 'non-interactive')
129
130
  .option('--offline', 'write local files only (no API calls)')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "convene-cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Convene CLI — AI development coordination bus client + UserPromptSubmit hook. Install: npm i -g convene-cli; then `convene setup`.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://dev.convene.live",