convene-cli 1.0.2 → 1.0.4

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.
@@ -174,6 +174,66 @@ function seedMemory(top, slug, baseUrl) {
174
174
  /* memory seed is best-effort */
175
175
  }
176
176
  }
177
+ // ── Cross-agent rule files ───────────────────────────────────────────────────
178
+ // AGENTS.md (written above) is the keystone — Codex, Windsurf, Cursor, and Cline
179
+ // auto-read it. These add each agent's NATIVE rule file so they reliably pick
180
+ // Convene up and call the `convene` CLI themselves. No per-prompt FRESH injection
181
+ // outside Claude Code (and, later, Codex) — see CONVENE_PROTOCOL.md.
182
+ function writeCursorRule(top, block) {
183
+ const file = node_path_1.default.join(top, '.cursor', 'rules', 'convene.mdc');
184
+ const frontmatter = '---\ndescription: Convene AI coordination bus — read channel context and post updates\nalwaysApply: true\n---\n\n';
185
+ const old = node_fs_1.default.existsSync(file) ? node_fs_1.default.readFileSync(file, 'utf8') : null;
186
+ const content = old !== null ? upsertMarkerBlock(old, block) : frontmatter + block + '\n';
187
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
188
+ const r = writeIfChanged(file, content);
189
+ log(`${r === 'unchanged' ? '·' : '✓'} .cursor/rules/convene.mdc (${r}) — Cursor auto-applies it every chat`);
190
+ }
191
+ function writeClineRule(top, block) {
192
+ const file = node_path_1.default.join(top, '.clinerules', 'convene.md');
193
+ const old = node_fs_1.default.existsSync(file) ? node_fs_1.default.readFileSync(file, 'utf8') : '';
194
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
195
+ const r = writeIfChanged(file, upsertMarkerBlock(old, block));
196
+ log(`${r === 'unchanged' ? '·' : '✓'} .clinerules/convene.md (${r}) — Cline workspace rule`);
197
+ }
198
+ function writeGeminiSettings(top) {
199
+ const file = node_path_1.default.join(top, '.gemini', 'settings.json');
200
+ let obj = {};
201
+ if (node_fs_1.default.existsSync(file)) {
202
+ try {
203
+ obj = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8')) || {};
204
+ }
205
+ catch {
206
+ log('· .gemini/settings.json (left as-is — unparseable JSON)');
207
+ return;
208
+ }
209
+ }
210
+ obj.context = obj.context && typeof obj.context === 'object' ? obj.context : {};
211
+ const cur = obj.context.fileName;
212
+ let names = Array.isArray(cur) ? cur : typeof cur === 'string' ? [cur] : [];
213
+ if (!names.includes('AGENTS.md'))
214
+ names = [...names, 'AGENTS.md'];
215
+ obj.context.fileName = names;
216
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
217
+ const r = writeIfChanged(file, JSON.stringify(obj, null, 2) + '\n');
218
+ log(`${r === 'unchanged' ? '·' : '✓'} .gemini/settings.json (${r}) — Gemini CLI reads AGENTS.md each prompt`);
219
+ }
220
+ function writeAiderConf(top) {
221
+ // No YAML parser bundled, so write-if-absent (don't risk clobbering an existing config).
222
+ const file = node_path_1.default.join(top, '.aider.conf.yml');
223
+ if (node_fs_1.default.existsSync(file)) {
224
+ log('· .aider.conf.yml (exists) — add `read: [AGENTS.md, CONVENE_PROTOCOL.md]` so Aider loads Convene');
225
+ return;
226
+ }
227
+ node_fs_1.default.writeFileSync(file, 'read:\n - AGENTS.md\n - CONVENE_PROTOCOL.md\n');
228
+ log('✓ .aider.conf.yml (created) — Aider loads the Convene instructions at startup');
229
+ }
230
+ function writeAgentRules(top, slug, member, baseUrl) {
231
+ const block = (0, protocol_1.conveneBlock)(slug, member, baseUrl);
232
+ writeCursorRule(top, block);
233
+ writeClineRule(top, block);
234
+ writeGeminiSettings(top);
235
+ writeAiderConf(top);
236
+ }
177
237
  async function init(opts) {
178
238
  const top = (0, git_1.gitToplevel)();
179
239
  if (!top)
@@ -185,6 +245,7 @@ async function init(opts) {
185
245
  const skipHook = opts.noHook === true || opts.hook === false;
186
246
  const skipGithook = opts.noGithook === true || opts.githook === false;
187
247
  const skipJoinToken = opts.noJoinToken === true || opts.joinToken === false;
248
+ const skipAgentRules = opts.noAgentRules === true || opts.agentRules === false;
188
249
  let slug = opts.slug || existing?.slug;
189
250
  let displayName = opts.name || existing?.displayName;
190
251
  let joinToken = existing?.joinToken; // reuse if present (keeps init idempotent)
@@ -296,11 +357,39 @@ async function init(opts) {
296
357
  else {
297
358
  log('· CONVENE_PROTOCOL.md (exists — left as-is)');
298
359
  }
299
- // 7. hook
360
+ // 7. hook (global, per-machine — fires in every repo, no-op outside convene repos)
300
361
  registerHook(skipHook);
362
+ // 7a. committed project hook — the repo CARRIES the same `convene fetch` hook in
363
+ // .claude/settings.json, so a teammate who opens it auto-connects (Claude Code
364
+ // applies project hooks after a one-time trust prompt; identical command de-dups
365
+ // against the global hook, so no double-fire).
366
+ if (skipHook) {
367
+ log('Skipped committed project hook (--no-hook).');
368
+ }
369
+ else {
370
+ const r = (0, hook_1.ensureProjectHookRegistered)(top);
371
+ if (r === 'manual') {
372
+ log('· .claude/settings.json left as-is (existing settings unparseable).');
373
+ }
374
+ else {
375
+ log(`${r === 'registered' ? '✓' : '·'} .claude/settings.json (committed \`convene fetch\` hook — teammates auto-connect on open)`);
376
+ if ((0, git_1.gitPathIsIgnored)(top, node_path_1.default.join('.claude', 'settings.json'))) {
377
+ log(' ⚠ .claude/settings.json is git-ignored — commit it (or drop the .claude ignore) so teammates inherit the hook;');
378
+ log(' otherwise they connect via `convene setup`.');
379
+ }
380
+ }
381
+ }
301
382
  // 7b. committed git pre-push hook — the tool-agnostic backstop that auto-posts a
302
383
  // [STATUS] when work is pushed (fires for Codex/Cowork/humans, not just Claude).
303
384
  installGitHookStep(top, skipGithook);
385
+ // 7c. Cross-agent rule files so Codex/Cursor/Cline/Gemini/Aider pick Convene up too
386
+ // (AGENTS.md already covers the auto-readers; these add each agent's native file).
387
+ if (skipAgentRules) {
388
+ log('Skipped cross-agent rule files (--no-agent-rules).');
389
+ }
390
+ else {
391
+ writeAgentRules(top, slug, member, baseUrl);
392
+ }
304
393
  // 8. memory seed (best-effort, outside the repo)
305
394
  seedMemory(top, slug, baseUrl);
306
395
  // 9. teammate one-liner
@@ -62,6 +62,13 @@ async function join(opts) {
62
62
  : hook === 'already'
63
63
  ? 'Hook already registered.'
64
64
  : 'Could not auto-register the hook — run `convene doctor --fix` or add it manually.');
65
+ // Ensure the repo carries the committed `convene fetch` hook (.claude/settings.json)
66
+ // so the next teammate auto-connects on open. Idempotent; de-dups with the global hook.
67
+ if (top) {
68
+ const pr = (0, hook_1.ensureProjectHookRegistered)(top);
69
+ if (pr === 'registered')
70
+ log('Wrote committed project hook (.claude/settings.json) — commit it so teammates auto-connect.');
71
+ }
65
72
  // The tool-agnostic backstop: auto-post a [STATUS] when you push (works even
66
73
  // for tools without a per-prompt hook).
67
74
  if (top) {
package/dist/hook.js CHANGED
@@ -10,6 +10,8 @@ exports.hookIsRegistered = hookIsRegistered;
10
10
  exports.withHook = withHook;
11
11
  exports.serializeSettings = serializeSettings;
12
12
  exports.ensureHookRegistered = ensureHookRegistered;
13
+ exports.projectSettingsPath = projectSettingsPath;
14
+ exports.ensureProjectHookRegistered = ensureProjectHookRegistered;
13
15
  /**
14
16
  * Claude Code UserPromptSubmit hook registration in ~/.claude/settings.json.
15
17
  *
@@ -81,3 +83,33 @@ function ensureHookRegistered() {
81
83
  return 'manual';
82
84
  }
83
85
  }
86
+ /** The repo's COMMITTED, shared project settings file (vs settings.local.json, which is personal/gitignored). */
87
+ function projectSettingsPath(toplevel) {
88
+ return node_path_1.default.join(toplevel, '.claude', 'settings.json');
89
+ }
90
+ /**
91
+ * Ensure the SAME `convene fetch` hook in the repo's committed `.claude/settings.json`,
92
+ * so a teammate who opens the repo gets it automatically (Claude Code applies
93
+ * project hooks after a one-time per-repo trust prompt). Identical to the global
94
+ * hook command, so Claude Code de-dups them — no double-fire if both are present.
95
+ * Idempotent + merge-safe: never clobbers other project settings.
96
+ */
97
+ function ensureProjectHookRegistered(toplevel) {
98
+ const p = projectSettingsPath(toplevel);
99
+ const raw = readSettingsRaw(p);
100
+ const settings = parseSettings(raw);
101
+ if (settings === null)
102
+ return 'manual'; // unparseable — don't clobber
103
+ if (hookIsRegistered(settings))
104
+ return 'already';
105
+ try {
106
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(p), { recursive: true });
107
+ if (raw !== null)
108
+ node_fs_1.default.writeFileSync(p + '.bak', raw);
109
+ node_fs_1.default.writeFileSync(p, serializeSettings(withHook(settings)));
110
+ return 'registered';
111
+ }
112
+ catch {
113
+ return 'manual';
114
+ }
115
+ }
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.2",
3
+ "version": "1.0.4",
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",