greatminds 0.1.0__py3-none-any.whl

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 (111) hide show
  1. greatminds/__init__.py +8 -0
  2. greatminds/cli/__init__.py +6 -0
  3. greatminds/cli/coord_init.py +324 -0
  4. greatminds/cli/coord_launch.py +258 -0
  5. greatminds/cli/coord_tmux.py +153 -0
  6. greatminds/cli/coordd.py +611 -0
  7. greatminds/cli/gate_check.py +261 -0
  8. greatminds/cli/inbox.py +257 -0
  9. greatminds/cli/intent_clean.py +113 -0
  10. greatminds/cli/lint_tokens.py +170 -0
  11. greatminds/cli/migrate_task.py +379 -0
  12. greatminds/cli/notify_from_journal.py +280 -0
  13. greatminds/cli/plan.py +167 -0
  14. greatminds/cli/pty_launch.py +261 -0
  15. greatminds/cli/render_role.py +129 -0
  16. greatminds/cli/stand.py +178 -0
  17. greatminds/cli/start_agent.py +473 -0
  18. greatminds/cli/stop_decide.py +166 -0
  19. greatminds/cli/task.py +1255 -0
  20. greatminds/cli/wake_check.py +306 -0
  21. greatminds/cli/watchdog.py +198 -0
  22. greatminds/core/__init__.py +6 -0
  23. greatminds/core/paths.py +100 -0
  24. greatminds/core/util.py +50 -0
  25. greatminds/data/COORDINATE.md +405 -0
  26. greatminds/data/PROJECT_VARIABLES.md +89 -0
  27. greatminds/data/__init__.py +10 -0
  28. greatminds/data/codex/profiles/architect-reviewer.config.toml +17 -0
  29. greatminds/data/codex/profiles/explorer.config.toml +18 -0
  30. greatminds/data/codex/profiles/technical-writer.config.toml +14 -0
  31. greatminds/data/command_START.yaml +743 -0
  32. greatminds/data/mcp/README.md +16 -0
  33. greatminds/data/mcp/canon.json +18 -0
  34. greatminds/data/plugins/README.md +28 -0
  35. greatminds/data/plugins/coordination-protocol/.claude-plugin/plugin.json +7 -0
  36. greatminds/data/plugins/coordination-protocol/skills/fsm-mechanics/SKILL.md +91 -0
  37. greatminds/data/plugins/coordination-protocol/skills/impl-block-craft/SKILL.md +103 -0
  38. greatminds/data/plugins/coordination-protocol/skills/inbox-and-escalation/SKILL.md +99 -0
  39. greatminds/data/plugins/coordination-protocol/skills/iteration-and-blocking/SKILL.md +107 -0
  40. greatminds/data/plugins/coordination-protocol/skills/plan-block-protocol/SKILL.md +106 -0
  41. greatminds/data/plugins/coordination-protocol/skills/stand-protocol/SKILL.md +123 -0
  42. greatminds/data/plugins/role-architect-planner/.claude-plugin/plugin.json +7 -0
  43. greatminds/data/plugins/role-architect-planner/skills/task-decomposition/SKILL.md +123 -0
  44. greatminds/data/plugins/role-architect-reviewer/.claude-plugin/plugin.json +7 -0
  45. greatminds/data/plugins/role-architect-reviewer/skills/commit-and-push-protocol/SKILL.md +170 -0
  46. greatminds/data/plugins/role-architect-reviewer/skills/evidence-chain-verification/SKILL.md +161 -0
  47. greatminds/data/plugins/role-architect-reviewer/skills/review-block-craft/SKILL.md +161 -0
  48. greatminds/data/plugins/role-architect-reviewer/skills/wake-and-unblock/SKILL.md +155 -0
  49. greatminds/data/plugins/role-explorer/.claude-plugin/plugin.json +7 -0
  50. greatminds/data/plugins/role-explorer/skills/bug-as-mini-task/SKILL.md +175 -0
  51. greatminds/data/plugins/role-explorer/skills/exploratory-probing/SKILL.md +183 -0
  52. greatminds/data/plugins/role-explorer/skills/re-verify-loop/SKILL.md +154 -0
  53. greatminds/data/plugins/role-maintainer/.claude-plugin/plugin.json +7 -0
  54. greatminds/data/plugins/role-maintainer/skills/agent-lifecycle-and-diagnostics/SKILL.md +124 -0
  55. greatminds/data/plugins/role-maintainer/skills/canon-sync-and-cutover/SKILL.md +117 -0
  56. greatminds/data/plugins/role-maintainer/skills/infra-surface-separation/SKILL.md +84 -0
  57. greatminds/data/plugins/role-maintainer/skills/maintainer-vs-planner-routing/SKILL.md +77 -0
  58. greatminds/data/plugins/role-reader/.claude-plugin/plugin.json +7 -0
  59. greatminds/data/plugins/role-reader/skills/audit-path-vs-post-write-path/SKILL.md +155 -0
  60. greatminds/data/plugins/role-reader/skills/reader-review-block-craft/SKILL.md +143 -0
  61. greatminds/data/plugins/role-stand-keeper/.claude-plugin/plugin.json +7 -0
  62. greatminds/data/plugins/role-stand-keeper/skills/fault-isolation-on-stand/SKILL.md +183 -0
  63. greatminds/data/plugins/role-stand-keeper/skills/fresh-db-volume-wipes/SKILL.md +156 -0
  64. greatminds/data/plugins/role-stand-keeper/skills/stand-bring-up/SKILL.md +140 -0
  65. greatminds/data/plugins/role-tester/.claude-plugin/plugin.json +7 -0
  66. greatminds/data/plugins/role-tester/skills/ui-visual-verification/SKILL.md +173 -0
  67. greatminds/data/roles/ARCHITECT-PLANNER.md +93 -0
  68. greatminds/data/roles/ARCHITECT-REVIEWER.md +68 -0
  69. greatminds/data/roles/BOT-DEVELOPER.md +43 -0
  70. greatminds/data/roles/BOT-USER.md +41 -0
  71. greatminds/data/roles/DEVELOPER.md +66 -0
  72. greatminds/data/roles/EXPLORER.md +74 -0
  73. greatminds/data/roles/MAINTAINER.md +122 -0
  74. greatminds/data/roles/READER.md +85 -0
  75. greatminds/data/roles/STAND-KEEPER.md +120 -0
  76. greatminds/data/roles/TECHNICAL-WRITER.md +64 -0
  77. greatminds/data/roles/TESTER.md +73 -0
  78. greatminds/data/roles/UI-DEVELOPER.md +84 -0
  79. greatminds/data/roles/USER.md +31 -0
  80. greatminds/data/schema.yaml +618 -0
  81. greatminds/data/templates/PROJECT.md.template +75 -0
  82. greatminds/data/templates/coordination/archive/.gitkeep +0 -0
  83. greatminds/data/templates/coordination/bot_archive/.gitkeep +1 -0
  84. greatminds/data/templates/coordination/bot_done/_TEMPLATE.md +26 -0
  85. greatminds/data/templates/coordination/bot_inbox/_TEMPLATE.md +35 -0
  86. greatminds/data/templates/coordination/bot_verified/.gitkeep +1 -0
  87. greatminds/data/templates/coordination/bot_wip/.gitkeep +1 -0
  88. greatminds/data/templates/coordination/feature_blocked/.gitkeep +1 -0
  89. greatminds/data/templates/coordination/feature_blocked/_TEMPLATE.md +30 -0
  90. greatminds/data/templates/coordination/feature_dev/.gitkeep +0 -0
  91. greatminds/data/templates/coordination/feature_docs/_TEMPLATE.md +23 -0
  92. greatminds/data/templates/coordination/feature_docs_review/_TEMPLATE.md +21 -0
  93. greatminds/data/templates/coordination/feature_inbox/_TEMPLATE.md +29 -0
  94. greatminds/data/templates/coordination/feature_plan/_TEMPLATE.md +42 -0
  95. greatminds/data/templates/coordination/feature_review/_TEMPLATE.md +32 -0
  96. greatminds/data/templates/coordination/feature_test/_TEMPLATE.md +44 -0
  97. greatminds/data/templates/coordination/feature_ui_dev/.gitkeep +1 -0
  98. greatminds/data/templates/coordination/inbox/.gitkeep +0 -0
  99. greatminds/data/templates/coordination/intent/.gitkeep +0 -0
  100. greatminds/data/templates/coordination/review_sessions/.gitkeep +0 -0
  101. greatminds/data/templates/coordination/review_sessions/_TEMPLATE.md +54 -0
  102. greatminds/data/templates/coordination/stand_done/_TEMPLATE.md +38 -0
  103. greatminds/data/templates/coordination/stand_requests/_TEMPLATE.md +43 -0
  104. greatminds/data/templates/coordination/stand_wip/.gitkeep +1 -0
  105. greatminds/data/templates/coordination/user_feedback/_TEMPLATE.md +32 -0
  106. greatminds/data/templates/coordination/verified/.gitkeep +0 -0
  107. greatminds-0.1.0.dist-info/METADATA +75 -0
  108. greatminds-0.1.0.dist-info/RECORD +111 -0
  109. greatminds-0.1.0.dist-info/WHEEL +4 -0
  110. greatminds-0.1.0.dist-info/entry_points.txt +20 -0
  111. greatminds-0.1.0.dist-info/licenses/LICENSE +202 -0
greatminds/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """greatminds — file-based multi-agent coordination protocol.
2
+
3
+ See https://github.com/veryviolet/greatminds for full docs.
4
+ """
5
+
6
+ __version__ = "0.1.0"
7
+
8
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """greatminds CLI entry-points.
2
+
3
+ Each module here exposes a ``main()`` function wired up as a ``project.scripts``
4
+ entry-point in ``pyproject.toml``. Modules are added incrementally as the
5
+ original ``/opt/coordination/bin/*`` scripts are ported.
6
+ """
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env python3
2
+ """coord-init — bootstrap coordination into a fresh project.
3
+
4
+ Usage:
5
+ coord-init [--project-dir <dir>] [--force]
6
+
7
+ Creates under <project-dir>:
8
+ bin/ symlinks (or copies) to canon scripts
9
+ coord.yaml from canon/coord.example.yaml (edit before launch)
10
+ coordination/
11
+ PROJECT.md from canon/templates/PROJECT.md.template
12
+ schema.yaml copy of canon/schema.yaml
13
+ .gitignore journal.ndjson, intent/, .agent_registry/, .locks/, .id_counter
14
+ feature_inbox/_TEMPLATE.yaml (and other queues)
15
+ feature_plan/, feature_dev/, feature_ui_dev/, feature_docs/,
16
+ feature_test/, feature_docs_review/, feature_review/,
17
+ feature_blocked/, verified/, archive/, user_feedback/,
18
+ review_sessions/, stand_requests/, stand_wip/, stand_done/,
19
+ intent/, inbox/<role>/
20
+
21
+ Idempotent: re-running on an initialised project copies missing pieces
22
+ only, never overwrites coordination/PROJECT.md or coord.yaml unless
23
+ --force is given.
24
+
25
+ After init:
26
+ 1. edit <project>/coord.yaml — set project_dir, tweak window list
27
+ 2. edit <project>/coordination/PROJECT.md — fill in tokens
28
+ 3. run bin/coord-tmux to spin up the tmux session
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import argparse
34
+ import os
35
+ import shutil
36
+ import sys
37
+ from pathlib import Path
38
+
39
+ from greatminds.core.util import die as _die_canonical
40
+
41
+
42
+ QUEUES = [
43
+ "feature_inbox", "feature_plan", "feature_dev", "feature_ui_dev",
44
+ "feature_docs", "feature_test", "feature_docs_review",
45
+ "feature_review", "feature_blocked", "verified", "archive",
46
+ "user_feedback", "review_sessions",
47
+ "stand_requests", "stand_wip", "stand_done",
48
+ "bot_inbox", "bot_wip", "bot_done", "bot_verified", "bot_archive",
49
+ ]
50
+
51
+ ROLES_LOWER = [
52
+ "architect-planner", "architect-reviewer", "developer", "ui-developer",
53
+ "technical-writer", "tester", "reader", "explorer", "stand-keeper",
54
+ "user", "maintainer", "bot-user", "bot-developer",
55
+ ]
56
+
57
+
58
+ def die(msg: str) -> None:
59
+ """Historical (msg-only) die signature preserved as a thin adapter."""
60
+ _die_canonical(1, msg)
61
+
62
+
63
+ def info(msg: str) -> None:
64
+ print(f" {msg}")
65
+
66
+
67
+ def ensure_dir(p: Path) -> str:
68
+ if p.is_dir():
69
+ return "exists"
70
+ p.mkdir(parents=True)
71
+ return "created"
72
+
73
+
74
+ def copy_if_missing(src: Path, dst: Path, force: bool = False) -> str:
75
+ if not src.is_file():
76
+ return "(canon source missing)"
77
+ if dst.is_file() and not force:
78
+ return "exists"
79
+ shutil.copyfile(src, dst)
80
+ if src.stat().st_mode & 0o111:
81
+ os.chmod(dst, dst.stat().st_mode | 0o755)
82
+ return "copied" if not dst.is_file() else "overwritten" if force else "copied"
83
+
84
+
85
+ def main(argv: list[str] | None = None) -> int:
86
+ """Entry point — wired up as ``greatminds-coord-init`` in pyproject.toml.
87
+
88
+ Bootstraps a project directory to use the coordination protocol:
89
+ creates the runtime queue tree, copies the schema / command / role
90
+ docs from the packaged ``greatminds.data`` directory, and seeds the
91
+ inbox + plugin overlay layout.
92
+
93
+ No ``bin/*`` symlinks are created. With pip/pipx-installed
94
+ ``greatminds``, the launcher and CLI tools (``greatminds-task``,
95
+ ``greatminds-coordd``, …) live in the venv's ``bin/`` and are on
96
+ PATH; per-project shims are unnecessary.
97
+ """
98
+ from greatminds.core.paths import find_canon_dir
99
+
100
+ ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
101
+ ap.add_argument("--project-dir", type=Path, default=Path.cwd())
102
+ ap.add_argument("--force", action="store_true",
103
+ help="overwrite coord.yaml and PROJECT.md if present")
104
+ args = ap.parse_args(argv)
105
+
106
+ canon = find_canon_dir()
107
+ proj = args.project_dir.resolve()
108
+ proj.mkdir(parents=True, exist_ok=True)
109
+ print(f"coord-init: bootstrapping {proj} from canon {canon}")
110
+
111
+ # project-root config (schema, command_START, role docs — canon-data,
112
+ # kept locally so humans can `cat <project>/DEVELOPER.md` without
113
+ # importing the package).
114
+ print("\nproject-root config:")
115
+ coord_example = canon / "coord.example.yaml"
116
+ if coord_example.is_file():
117
+ info(f"coord.yaml: {copy_if_missing(coord_example, proj / 'coord.yaml', args.force)}")
118
+ info(f"schema.yaml: {copy_if_missing(canon / 'schema.yaml', proj / 'schema.yaml', force=True)}")
119
+ info(f"command_START.yaml: {copy_if_missing(canon / 'command_START.yaml', proj / 'command_START.yaml', force=True)}")
120
+ info(f"COORDINATE.md: {copy_if_missing(canon / 'COORDINATE.md', proj / 'COORDINATE.md', force=True)}")
121
+ # role docs at project root (sourced from packaged greatminds.data/roles/)
122
+ for role_md in ("ARCHITECT-PLANNER.md", "ARCHITECT-REVIEWER.md", "DEVELOPER.md",
123
+ "UI-DEVELOPER.md", "TECHNICAL-WRITER.md", "TESTER.md", "READER.md",
124
+ "EXPLORER.md", "STAND-KEEPER.md", "MAINTAINER.md", "USER.md",
125
+ "BOT-USER.md", "BOT-DEVELOPER.md"):
126
+ src = canon / "roles" / role_md
127
+ if src.is_file():
128
+ copy_if_missing(src, proj / role_md, force=True)
129
+
130
+ # coordination/ — runtime state (queues, journal, intent, inbox)
131
+ coord = proj / "coordination"
132
+ print(f"\ncoordination/ (runtime state):")
133
+ info(f"dir: {ensure_dir(coord)}")
134
+ info(f"PROJECT.md: {copy_if_missing(canon / 'templates' / 'PROJECT.md.template', coord / 'PROJECT.md', args.force)}")
135
+ # .gitignore
136
+ gi = coord / ".gitignore"
137
+ if not gi.is_file() or args.force:
138
+ gi.write_text(
139
+ "# Runtime churn — NOT version-controlled. Everything else\n"
140
+ "# under coordination/ (PROJECT.md, queue task files,\n"
141
+ "# verified/archive history, templates) IS tracked.\n"
142
+ "journal.ndjson\n"
143
+ ".notify_state.json\n"
144
+ "intent/\n"
145
+ ".agent_registry/\n"
146
+ ".locks/\n"
147
+ ".id_counter\n"
148
+ "heartbeat.*\n"
149
+ "inbox/*/*\n"
150
+ "!inbox/*/.gitkeep\n"
151
+ "*.legacy\n"
152
+ "PROJECT.env\n",
153
+ encoding="utf-8",
154
+ )
155
+ info(".gitignore: written")
156
+ else:
157
+ info(".gitignore: exists")
158
+
159
+ # queue dirs + gitkeep
160
+ print("\nqueues:")
161
+ for q in QUEUES:
162
+ st = ensure_dir(coord / q)
163
+ gk = coord / q / ".gitkeep"
164
+ if not gk.is_file():
165
+ gk.touch()
166
+ info(f"{q}: {st}")
167
+ ensure_dir(coord / "intent")
168
+ info("intent: created/exists")
169
+
170
+ # inbox per role
171
+ inbox = coord / "inbox"
172
+ ensure_dir(inbox)
173
+ print("\ninbox per role:")
174
+ for r in ROLES_LOWER:
175
+ d = inbox / r
176
+ ensure_dir(d)
177
+ gk = d / ".gitkeep"
178
+ if not gk.is_file():
179
+ gk.touch()
180
+ info(f"{r}: created/exists")
181
+
182
+ # locks + agent_registry dirs (gitignored)
183
+ ensure_dir(coord / ".locks")
184
+ ensure_dir(coord / ".agent_registry")
185
+
186
+ # Project-side plugin overlay. Canon plugins live in
187
+ # /opt/coordination/plugins/ and are loaded directly via --plugin-dir;
188
+ # this overlay is the project's per-install override layer.
189
+ print("\nplugin overlay (project-overrides):")
190
+ overlay = coord / "plugins.local" / "project-overrides"
191
+ overlay_meta = overlay / ".claude-plugin"
192
+ overlay_skills = overlay / "skills"
193
+ ensure_dir(overlay)
194
+ ensure_dir(overlay_meta)
195
+ ensure_dir(overlay_skills)
196
+ pj = overlay_meta / "plugin.json"
197
+ if not pj.is_file():
198
+ pj.write_text(
199
+ '{\n'
200
+ ' "name": "project-overrides",\n'
201
+ ' "version": "0.1.0",\n'
202
+ ' "description": "Project-side overlay for canon coordination plugins. Add SKILL.md under skills/<name>/ to override (same name) or extend (new name) canon skills. To disable a canon skill instead, use skillOverrides in <project>/.claude/settings.local.json.",\n'
203
+ ' "author": { "name": "project-local" }\n'
204
+ '}\n',
205
+ encoding="utf-8",
206
+ )
207
+ info("plugin.json: written")
208
+ else:
209
+ info("plugin.json: exists")
210
+ sg = overlay_skills / ".gitkeep"
211
+ if not sg.is_file():
212
+ sg.touch()
213
+ rdme = overlay / "README.md"
214
+ if not rdme.is_file():
215
+ rdme.write_text(
216
+ "# project-overrides\n\n"
217
+ "Project-side overlay for canon coordination plugins. Loaded LAST\n"
218
+ "by `bin/start_agent --plugin-dir` so last-wins precedence applies.\n\n"
219
+ "## Three usage patterns\n\n"
220
+ "**Replace a canon skill (override by name).** Create\n"
221
+ "`skills/<same-name>/SKILL.md` with the same `name:` in its\n"
222
+ "frontmatter as the canon skill — your project skill shadows\n"
223
+ "the canon one.\n\n"
224
+ "**Disable a canon skill.** Edit\n"
225
+ "`<project>/.claude/settings.local.json` and add a `skillOverrides`\n"
226
+ "field: `{\"<canon-skill-name>\": \"off\"}`.\n\n"
227
+ "**Add a project-only skill.** Create `skills/<new-name>/SKILL.md`\n"
228
+ "with a unique `name:`. It joins the loaded skill pool for this\n"
229
+ "project; no canon counterpart needed.\n",
230
+ encoding="utf-8",
231
+ )
232
+ info("README.md: written")
233
+ else:
234
+ info("README.md: exists")
235
+
236
+ # mcp.local.json — empty stub; projects add per-project MCP servers
237
+ # here. Canon MCPs come from /opt/coordination/mcp/canon.json.
238
+ mcpl = coord / "mcp.local.json"
239
+ if not mcpl.is_file():
240
+ mcpl.write_text('{\n "mcpServers": {}\n}\n', encoding="utf-8")
241
+ info("mcp.local.json: written")
242
+ else:
243
+ info("mcp.local.json: exists")
244
+
245
+ # PROJECT.env.example — committed template; real PROJECT.env (gitignored)
246
+ # is copied from this and filled in per-environment with secrets.
247
+ pe_ex = coord / "PROJECT.env.example"
248
+ if not pe_ex.is_file():
249
+ pe_ex.write_text(
250
+ "# coordination/PROJECT.env.example — TEMPLATE for the real PROJECT.env.\n"
251
+ "#\n"
252
+ "# Copy this file to PROJECT.env (gitignored) and fill in real values.\n"
253
+ "# bin/start_agent sources PROJECT.env before launching Claude/codex/cursor\n"
254
+ "# so MCP servers and skill Bash blocks resolve ${VAR} via the env.\n"
255
+ "#\n"
256
+ "# Canon token-contract (full list + semantics in\n"
257
+ "# <canon>/templates/PROJECT.md.template):\n"
258
+ "\n"
259
+ "# Project root (usually exported automatically by start_agent; set\n"
260
+ "# explicitly only if you need a different value than $COORD_PROJECT_DIR).\n"
261
+ "#PROJECT_ROOT=/opt/your_project\n"
262
+ "\n"
263
+ "# Postgres DSN with credentials (used by postgres MCP server).\n"
264
+ "# Example: postgresql://user:password@host:5432/dbname\n"
265
+ "COORD_POSTGRES_DSN=\n"
266
+ "\n"
267
+ "# Stand hostnames (if your project has a stand).\n"
268
+ "STAND_HOST_A=\n"
269
+ "STAND_HOST_B=\n"
270
+ "\n"
271
+ "# Stand REST API base URLs.\n"
272
+ "STAND_URL_A=\n"
273
+ "STAND_URL_B=\n",
274
+ encoding="utf-8",
275
+ )
276
+ info("PROJECT.env.example: written")
277
+ else:
278
+ info("PROJECT.env.example: exists")
279
+
280
+ # <project>/.claude/settings.local.json — Claude Code picks this up
281
+ # from cwd automatically. Create with standard coordination hooks
282
+ # only if absent; existing files are left alone.
283
+ #
284
+ # Hook commands reference the installed entry-points by name; they
285
+ # must be on PATH (which pip/pipx install puts them on for the env
286
+ # the user runs Claude Code from).
287
+ cclaude = proj / ".claude"
288
+ ensure_dir(cclaude)
289
+ sl = cclaude / "settings.local.json"
290
+ if not sl.is_file():
291
+ # .format() template: {{...}} → literal {...}, single braces are subs.
292
+ # The Stop hook command contains a shell expansion ${COORD_ROLE:-UNKNOWN}
293
+ # which must reach the final file as $X; we wrap each side as {{ }}.
294
+ settings_tpl = (
295
+ "{{\n"
296
+ ' "permissions": {{ "allow": [] }},\n'
297
+ ' "autoMode": {{ "allow": ["$defaults"] }},\n'
298
+ ' "hooks": {{\n'
299
+ ' "Stop": [{{\n'
300
+ ' "matcher": "",\n'
301
+ ' "hooks": [{{\n'
302
+ ' "type": "command",\n'
303
+ ' "command": "greatminds-stop-decide \\"${{COORD_ROLE:-UNKNOWN}}\\" --host claude --project-dir {proj}"\n'
304
+ ' }}]\n'
305
+ ' }}]\n'
306
+ ' }}\n'
307
+ "}}\n"
308
+ )
309
+ sl.write_text(settings_tpl.format(proj=str(proj)), encoding="utf-8")
310
+ info(".claude/settings.local.json: written")
311
+ else:
312
+ info(".claude/settings.local.json: exists")
313
+
314
+ print("\ndone.")
315
+ print()
316
+ print("Next:")
317
+ print(f" 1. edit {proj}/coord.yaml — confirm project_dir, window list")
318
+ print(f" 2. edit {proj}/coordination/PROJECT.md — fill in tokens")
319
+ print(f" 3. run: cd {proj} && greatminds-coord-tmux")
320
+ return 0
321
+
322
+
323
+ if __name__ == "__main__":
324
+ sys.exit(main())
@@ -0,0 +1,258 @@
1
+ """coord-launch — multi-frontend launcher for the coordination fleet.
2
+
3
+ Currently supports:
4
+
5
+ ``tmux`` Same as ``greatminds-coord-tmux`` — build a tmux session,
6
+ one window per role, agent launcher pre-typed. Use that
7
+ binary directly if you only need the tmux target.
8
+
9
+ ``vscode`` Generate ``.vscode/tasks.json`` and a workspace file at
10
+ the project root. One task per agent role, each agent
11
+ runs in its own dedicated terminal panel. User opens
12
+ the project with ``code .`` then launches tasks via
13
+ ``Cmd+Shift+P → Tasks: Run Task → agent: <role>``.
14
+
15
+ ``cursor-ide`` Same generated files as ``vscode`` (Cursor IDE is a
16
+ VS Code fork and reads the same ``.vscode/`` config).
17
+ Prints ``cursor .`` as the open command instead.
18
+
19
+ The IDE targets are written as a foundation — terminals work, env vars
20
+ propagate, but advanced features like auto-attach to existing sessions or
21
+ panel grouping are best-effort. PRs welcome.
22
+
23
+ Usage::
24
+
25
+ greatminds-coord-launch --target {tmux,vscode,cursor-ide} [--config <coord.yaml>] [--project-dir <dir>]
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import argparse
31
+ import json
32
+ import shutil
33
+ import sys
34
+ from pathlib import Path
35
+
36
+ import yaml
37
+
38
+ from greatminds.core.util import die
39
+
40
+
41
+ # Status colour for the agent terminals in VS Code (cycled per agent so they
42
+ # stand out side-by-side). Names from VS Code's standard theme colour set.
43
+ VSCODE_PANEL_COLOURS = [
44
+ "terminal.ansiBlue",
45
+ "terminal.ansiGreen",
46
+ "terminal.ansiYellow",
47
+ "terminal.ansiMagenta",
48
+ "terminal.ansiCyan",
49
+ "terminal.ansiRed",
50
+ ]
51
+
52
+
53
+ def load_coord_yaml(cfg_path: Path) -> dict:
54
+ """Parse a coord.yaml file or die with a clear error."""
55
+ if not cfg_path.is_file():
56
+ die(1, f"coord.yaml not found at {cfg_path} "
57
+ "(pass --config or run from the project root)")
58
+ try:
59
+ data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
60
+ except yaml.YAMLError as exc:
61
+ die(1, f"coord.yaml parse error: {exc}")
62
+ raise SystemExit
63
+ if not isinstance(data, dict):
64
+ die(1, "coord.yaml root must be a mapping")
65
+ raise SystemExit
66
+ return data
67
+
68
+
69
+ def resolve_launcher() -> str:
70
+ """Return the absolute path to ``greatminds-start-agent`` or die.
71
+
72
+ IDE-target tasks reference this command verbatim, so it must be on PATH
73
+ at coord-launch time too (we resolve it now to give a clear error rather
74
+ than letting VS Code's task runner fail opaquely later).
75
+ """
76
+ p = shutil.which("greatminds-start-agent")
77
+ if p is None:
78
+ die(1, "greatminds-start-agent not on PATH. "
79
+ "Install greatminds (pip/pipx) first.")
80
+ raise SystemExit
81
+ return p
82
+
83
+
84
+ def emit_vscode(project_dir: Path, cfg: dict, *, ide_label: str) -> None:
85
+ """Write ``.vscode/tasks.json`` (and a workspace file) for VS Code / Cursor IDE."""
86
+ launcher = resolve_launcher()
87
+
88
+ windows = cfg.get("windows") or []
89
+ if not isinstance(windows, list) or not windows:
90
+ die(1, "coord.yaml: windows must be a non-empty list")
91
+
92
+ vscode_dir = project_dir / ".vscode"
93
+ vscode_dir.mkdir(exist_ok=True)
94
+
95
+ tasks: list[dict] = []
96
+ colour_idx = 0
97
+ for w in windows:
98
+ name = w.get("name") or ""
99
+ role = (w.get("role") or "").upper()
100
+ tool = w.get("tool") or "bash"
101
+ mode = w.get("mode") or ""
102
+ if not name:
103
+ die(1, f"window without name: {w}")
104
+
105
+ # Bash / no-role windows: just open a project shell, no agent launch.
106
+ if tool == "bash" or not role:
107
+ task = {
108
+ "label": f"shell: {name}",
109
+ "type": "shell",
110
+ "command": "${env:SHELL}",
111
+ "presentation": {
112
+ "echo": True,
113
+ "reveal": "always",
114
+ "panel": "dedicated",
115
+ "group": "agents",
116
+ "showReuseMessage": False,
117
+ "clear": False,
118
+ },
119
+ "options": {
120
+ "cwd": "${workspaceFolder}",
121
+ "env": {
122
+ "COORD_PROJECT_DIR": "${workspaceFolder}",
123
+ },
124
+ },
125
+ "problemMatcher": [],
126
+ "runOptions": {"runOn": "default"},
127
+ }
128
+ tasks.append(task)
129
+ continue
130
+
131
+ cmd_args = [role, tool]
132
+ if mode:
133
+ cmd_args += ["--mode", mode]
134
+ cmd_line = launcher + " " + " ".join(cmd_args)
135
+
136
+ colour = VSCODE_PANEL_COLOURS[colour_idx % len(VSCODE_PANEL_COLOURS)]
137
+ colour_idx += 1
138
+
139
+ task = {
140
+ "label": f"agent: {name}",
141
+ "detail": f"{role} via {tool}" + (f" ({mode})" if mode else ""),
142
+ "type": "shell",
143
+ "command": cmd_line,
144
+ "presentation": {
145
+ "echo": True,
146
+ "reveal": "always",
147
+ "panel": "dedicated",
148
+ "group": "agents",
149
+ "showReuseMessage": False,
150
+ "clear": False,
151
+ },
152
+ "options": {
153
+ "cwd": "${workspaceFolder}",
154
+ "env": {
155
+ "COORD_PROJECT_DIR": "${workspaceFolder}",
156
+ "COORD_ROLE": role,
157
+ },
158
+ },
159
+ "problemMatcher": [],
160
+ "runOptions": {"runOn": "default"},
161
+ # VS Code allows colorising the terminal status bar per task.
162
+ "icon": {"id": "terminal", "color": colour},
163
+ }
164
+ tasks.append(task)
165
+
166
+ tasks_doc = {
167
+ "version": "2.0.0",
168
+ "tasks": tasks,
169
+ }
170
+ tasks_file = vscode_dir / "tasks.json"
171
+ tasks_file.write_text(json.dumps(tasks_doc, indent=2) + "\n", encoding="utf-8")
172
+
173
+ # Workspace file — opens VS Code with the project root and an attractive title.
174
+ session_name = cfg.get("session") or "agents"
175
+ workspace = {
176
+ "folders": [{"path": "."}],
177
+ "settings": {
178
+ "window.title": f"{session_name} — greatminds fleet",
179
+ },
180
+ }
181
+ ws_file = project_dir / f"{session_name}.code-workspace"
182
+ ws_file.write_text(json.dumps(workspace, indent=2) + "\n", encoding="utf-8")
183
+
184
+ print(f"wrote {tasks_file.relative_to(project_dir)} "
185
+ f"({len(tasks)} tasks)")
186
+ print(f"wrote {ws_file.relative_to(project_dir)}")
187
+ print()
188
+ print(f"open in {ide_label}:")
189
+ open_cmd = "code" if ide_label == "VS Code" else "cursor"
190
+ print(f" {open_cmd} {ws_file.name}")
191
+ print()
192
+ print("then launch each agent terminal via:")
193
+ print(" Cmd/Ctrl+Shift+P → Tasks: Run Task → agent: <name>")
194
+ print()
195
+ print("each task opens a dedicated terminal panel with COORD_ROLE and "
196
+ "COORD_PROJECT_DIR pre-exported.")
197
+
198
+
199
+ def emit_tmux_via_existing(args: argparse.Namespace) -> int:
200
+ """Delegate to greatminds-coord-tmux (the existing tmux target)."""
201
+ from greatminds.cli.coord_tmux import main as tmux_main
202
+
203
+ fwd: list[str] = []
204
+ if args.config is not None:
205
+ fwd += ["--config", str(args.config)]
206
+ if args.project_dir is not None:
207
+ fwd += ["--project-dir", str(args.project_dir)]
208
+ if args.recreate:
209
+ fwd.append("--recreate")
210
+ return tmux_main(fwd)
211
+
212
+
213
+ def main(argv: list[str] | None = None) -> int:
214
+ """Entry point — wired up as ``greatminds-coord-launch`` in pyproject.toml."""
215
+ ap = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else "")
216
+ ap.add_argument("--target", choices=["tmux", "vscode", "cursor-ide"],
217
+ default="tmux",
218
+ help="frontend to launch the fleet in (default: tmux)")
219
+ ap.add_argument("--config", type=Path,
220
+ help="path to coord.yaml (default: <project>/coord.yaml)")
221
+ ap.add_argument("--project-dir", type=Path,
222
+ help="override config.project_dir / cwd")
223
+ ap.add_argument("--recreate", action="store_true",
224
+ help="(tmux target only) kill existing session and rebuild")
225
+ args = ap.parse_args(argv)
226
+
227
+ if args.target == "tmux":
228
+ return emit_tmux_via_existing(args)
229
+
230
+ # vscode / cursor-ide: locate coord.yaml.
231
+ cfg_path = args.config
232
+ if cfg_path is None:
233
+ for p in (Path.cwd() / "coord.yaml",
234
+ Path.cwd() / "coordination" / "coord.yaml"):
235
+ if p.is_file():
236
+ cfg_path = p
237
+ break
238
+ if cfg_path is None or not cfg_path.is_file():
239
+ die(1, "coord.yaml not found (pass --config or run from project root)")
240
+ return 1
241
+
242
+ cfg = load_coord_yaml(cfg_path)
243
+ project_dir = (args.project_dir or Path(cfg.get("project_dir") or ".")).resolve()
244
+ if not project_dir.is_dir():
245
+ die(1, f"project_dir {project_dir} not found")
246
+ return 1
247
+ if not (project_dir / "coordination").is_dir():
248
+ die(1, f"{project_dir}/coordination/ not found "
249
+ "(run greatminds-coord-init first)")
250
+ return 1
251
+
252
+ ide_label = "VS Code" if args.target == "vscode" else "Cursor IDE"
253
+ emit_vscode(project_dir, cfg, ide_label=ide_label)
254
+ return 0
255
+
256
+
257
+ if __name__ == "__main__":
258
+ sys.exit(main())