godpowers 2.0.0 → 2.1.1

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 (53) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +141 -0
  3. package/README.md +45 -5
  4. package/RELEASE.md +30 -48
  5. package/SKILL.md +9 -1
  6. package/agents/god-design-reviewer.md +6 -6
  7. package/agents/god-designer.md +1 -1
  8. package/agents/god-executor.md +23 -0
  9. package/agents/god-quality-reviewer.md +12 -1
  10. package/agents/god-spec-reviewer.md +10 -0
  11. package/bin/install.js +119 -655
  12. package/extensions/launch-pack/README.md +1 -1
  13. package/lib/README.md +16 -0
  14. package/lib/agent-browser-driver.js +13 -13
  15. package/lib/agent-cache.js +8 -1
  16. package/lib/agent-refs.js +161 -0
  17. package/lib/budget.js +25 -11
  18. package/lib/context-writer.js +17 -6
  19. package/lib/events.js +11 -4
  20. package/lib/extension-authoring.js +27 -0
  21. package/lib/feature-awareness.js +18 -0
  22. package/lib/fs-async.js +28 -0
  23. package/lib/installer-args.js +99 -0
  24. package/lib/installer-core.js +345 -0
  25. package/lib/installer-files.js +80 -0
  26. package/lib/installer-runtimes.js +112 -0
  27. package/lib/intent.js +111 -16
  28. package/lib/release-surface-sync.js +8 -1
  29. package/lib/repo-surface-sync.js +9 -2
  30. package/lib/review-required.js +2 -1
  31. package/lib/router.js +23 -3
  32. package/lib/skill-surface.js +42 -0
  33. package/lib/state-lock.js +10 -0
  34. package/lib/state.js +101 -8
  35. package/lib/workflow-runner.js +42 -5
  36. package/package.json +4 -3
  37. package/references/HAVE-NOTS.md +4 -3
  38. package/references/orchestration/GOD-MODE-RUNBOOK.md +273 -0
  39. package/routing/god-arch.yaml +1 -1
  40. package/routing/god-build.yaml +1 -1
  41. package/skills/god-add-backlog.md +1 -1
  42. package/skills/god-agent-audit.md +2 -2
  43. package/skills/god-build.md +5 -3
  44. package/skills/god-context-scan.md +2 -3
  45. package/skills/god-design.md +2 -2
  46. package/skills/god-doctor.md +2 -2
  47. package/skills/god-help.md +4 -3
  48. package/skills/god-mode.md +10 -266
  49. package/skills/god-org-context.md +1 -1
  50. package/skills/god-repair.md +3 -3
  51. package/skills/god-review.md +9 -0
  52. package/skills/god-stories.md +1 -1
  53. package/skills/god-version.md +2 -2
package/bin/install.js CHANGED
@@ -1,137 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Godpowers Installer
4
+ * Godpowers installer and local CLI helpers.
5
5
  *
6
- * Installs Godpowers globally for supported AI coding tools.
7
- * Usage: npx godpowers [options]
6
+ * The executable stays intentionally thin. Runtime definitions, argument
7
+ * parsing, and install/uninstall file operations live in lib/ so they can be
8
+ * tested and reused without shelling through the binary.
8
9
  */
9
10
 
10
- const fs = require('fs');
11
11
  const path = require('path');
12
- const os = require('os');
12
+
13
+ const { parseArgs } = require('../lib/installer-args');
14
+ const { RUNTIMES, runtimeKeys } = require('../lib/installer-runtimes');
15
+ const {
16
+ installForRuntime,
17
+ uninstallForRuntime,
18
+ countInstalledSurface
19
+ } = require('../lib/installer-core');
13
20
 
14
21
  const VERSION = require('../package.json').version;
15
22
 
16
23
  const BANNER = `
17
- ██████╗ ██████╗ ██████╗
18
- ██╔════╝ ██╔═══██╗██╔══██╗
19
- ██║ ███╗██║ ██║██║ ██║
20
- ██║ ██║██║ ██║██║ ██║
21
- ╚██████╔╝╚██████╔╝██████╔╝
22
- ╚═════╝ ╚═════╝ ╚═════╝
23
24
  GODPOWERS v${VERSION}
24
25
  Ship fast. Ship right. Ship everything.
25
26
  `;
26
27
 
27
- const RUNTIMES = {
28
- claude: {
29
- name: 'Claude Code',
30
- configDir: path.join(os.homedir(), '.claude'),
31
- skillsDir: 'skills',
32
- configFile: null,
33
- },
34
- codex: {
35
- name: 'Codex',
36
- configDir: path.join(os.homedir(), '.codex'),
37
- skillsDir: 'skills',
38
- configFile: 'config.toml',
39
- agentMetadata: 'toml',
40
- },
41
- cursor: {
42
- name: 'Cursor',
43
- configDir: path.join(os.homedir(), '.cursor'),
44
- skillsDir: 'rules',
45
- configFile: null,
46
- },
47
- windsurf: {
48
- name: 'Windsurf',
49
- configDir: path.join(os.homedir(), '.windsurf'),
50
- skillsDir: 'rules',
51
- configFile: null,
52
- },
53
- opencode: {
54
- name: 'OpenCode',
55
- configDir: path.join(os.homedir(), '.opencode'),
56
- skillsDir: 'skills',
57
- configFile: null,
58
- },
59
- gemini: {
60
- name: 'Gemini CLI',
61
- configDir: path.join(os.homedir(), '.gemini'),
62
- skillsDir: 'skills',
63
- configFile: null,
64
- },
65
- copilot: {
66
- name: 'GitHub Copilot',
67
- configDir: path.join(os.homedir(), '.copilot'),
68
- skillsDir: 'skills',
69
- configFile: null,
70
- },
71
- augment: {
72
- name: 'Augment',
73
- configDir: path.join(os.homedir(), '.augment'),
74
- skillsDir: 'skills',
75
- configFile: null,
76
- },
77
- trae: {
78
- name: 'Trae',
79
- configDir: path.join(os.homedir(), '.trae'),
80
- skillsDir: 'skills',
81
- configFile: null,
82
- },
83
- cline: {
84
- name: 'Cline',
85
- configDir: path.join(os.homedir(), '.cline'),
86
- skillsDir: 'skills',
87
- configFile: null,
88
- },
89
- kilo: {
90
- name: 'Kilo',
91
- configDir: path.join(os.homedir(), '.kilo'),
92
- skillsDir: 'skills',
93
- configFile: null,
94
- },
95
- antigravity: {
96
- name: 'Antigravity',
97
- configDir: path.join(os.homedir(), '.antigravity'),
98
- skillsDir: 'skills',
99
- configFile: null,
100
- },
101
- qwen: {
102
- name: 'Qwen Code',
103
- configDir: path.join(os.homedir(), '.qwen'),
104
- skillsDir: 'skills',
105
- configFile: null,
106
- },
107
- codebuddy: {
108
- name: 'CodeBuddy',
109
- configDir: path.join(os.homedir(), '.codebuddy'),
110
- skillsDir: 'skills',
111
- configFile: null,
112
- },
113
- pi: {
114
- name: 'Pi',
115
- configDir: path.join(os.homedir(), '.pi'),
116
- skillsDir: 'skills',
117
- configFile: null,
118
- },
119
- };
120
-
121
- function resolveRuntime(runtimeKey, opts = {}) {
122
- const runtime = RUNTIMES[runtimeKey];
123
- if (!runtime) return null;
124
- const resolved = { ...runtime };
125
- if (opts.local && !opts.global) {
126
- resolved.configDir = path.join(process.cwd(), path.basename(runtime.configDir));
127
- }
128
- return resolved;
129
- }
130
-
131
- // ---------------------------------------------------------------------------
132
- // Helpers
133
- // ---------------------------------------------------------------------------
134
-
135
28
  function log(msg) {
136
29
  console.log(` ${msg}`);
137
30
  }
@@ -148,447 +41,6 @@ function error(msg) {
148
41
  console.error(` \x1b[31mx\x1b[0m ${msg}`);
149
42
  }
150
43
 
151
- function ensureDir(dir) {
152
- if (!fs.existsSync(dir)) {
153
- fs.mkdirSync(dir, { recursive: true });
154
- }
155
- }
156
-
157
- function copyRecursive(src, dest) {
158
- ensureDir(dest);
159
- const entries = fs.readdirSync(src, { withFileTypes: true });
160
- for (const entry of entries) {
161
- const srcPath = path.join(src, entry.name);
162
- const destPath = path.join(dest, entry.name);
163
- if (entry.isDirectory()) {
164
- copyRecursive(srcPath, destPath);
165
- } else {
166
- fs.copyFileSync(srcPath, destPath);
167
- }
168
- }
169
- }
170
-
171
- function copyRuntimeBundle(srcDir, destDir) {
172
- ensureDir(destDir);
173
- for (const dir of ['lib', 'routing', 'workflows', 'schema', 'templates', 'references']) {
174
- const src = path.join(srcDir, dir);
175
- if (fs.existsSync(src)) {
176
- copyRecursive(src, path.join(destDir, dir));
177
- }
178
- }
179
- const packageJson = path.join(srcDir, 'package.json');
180
- if (fs.existsSync(packageJson)) {
181
- fs.copyFileSync(packageJson, path.join(destDir, 'package.json'));
182
- }
183
- }
184
-
185
- function installSkillFile(srcFile, skillsDest, runtimeKey, targetName = null) {
186
- const baseName = targetName || path.basename(srcFile, '.md');
187
- if (runtimeKey === 'codex') {
188
- const skillDir = path.join(skillsDest, baseName);
189
- if (fs.existsSync(skillDir)) {
190
- fs.rmSync(skillDir, { recursive: true, force: true });
191
- }
192
- ensureDir(skillDir);
193
- fs.copyFileSync(srcFile, path.join(skillDir, 'SKILL.md'));
194
- return;
195
- }
196
- fs.copyFileSync(srcFile, path.join(skillsDest, `${baseName}.md`));
197
- }
198
-
199
- function parseAgentFrontmatter(content) {
200
- const fallback = { name: null, description: null };
201
- if (!content.startsWith('---\n')) return fallback;
202
-
203
- const end = content.indexOf('\n---', 4);
204
- if (end === -1) return fallback;
205
-
206
- const lines = content.slice(4, end).split('\n');
207
- const parsed = { ...fallback };
208
-
209
- for (let i = 0; i < lines.length; i++) {
210
- const line = lines[i];
211
- const nameMatch = line.match(/^name:\s*(.+)\s*$/);
212
- if (nameMatch) {
213
- parsed.name = nameMatch[1].replace(/^["']|["']$/g, '');
214
- continue;
215
- }
216
-
217
- if (line === 'description: |') {
218
- const desc = [];
219
- i++;
220
- while (i < lines.length && /^ {2}/.test(lines[i])) {
221
- desc.push(lines[i].slice(2));
222
- i++;
223
- }
224
- i--;
225
- parsed.description = desc.join('\n').trim();
226
- continue;
227
- }
228
-
229
- const descMatch = line.match(/^description:\s*(.+)\s*$/);
230
- if (descMatch) {
231
- parsed.description = descMatch[1].replace(/^["']|["']$/g, '');
232
- }
233
- }
234
-
235
- return parsed;
236
- }
237
-
238
- function stripFrontmatter(content) {
239
- if (!content.startsWith('---\n')) return content.trim();
240
- const end = content.indexOf('\n---', 4);
241
- if (end === -1) return content.trim();
242
- return content.slice(end + 4).trim();
243
- }
244
-
245
- function tomlString(value) {
246
- return JSON.stringify(value || '');
247
- }
248
-
249
- function tomlLiteral(value) {
250
- return `'''\n${(value || '').replace(/'''/g, "'''\\'''")}\n'''`;
251
- }
252
-
253
- function writeCodexAgentToml(srcFile, agentsDest) {
254
- const content = fs.readFileSync(srcFile, 'utf8');
255
- const frontmatter = parseAgentFrontmatter(content);
256
- const name = frontmatter.name || path.basename(srcFile, '.md');
257
- const description = frontmatter.description || `Godpowers specialist agent: ${name}.`;
258
- const instructions = stripFrontmatter(content);
259
- const toml = [
260
- `name = ${tomlString(name)}`,
261
- `description = ${tomlString(description)}`,
262
- 'sandbox_mode = "workspace-write"',
263
- `developer_instructions = ${tomlLiteral(instructions)}`,
264
- ''
265
- ].join('\n');
266
-
267
- fs.writeFileSync(path.join(agentsDest, `${name}.toml`), toml);
268
- }
269
-
270
- function installAgentFile(srcFile, agentsDest, runtime) {
271
- fs.copyFileSync(srcFile, path.join(agentsDest, path.basename(srcFile)));
272
- if (runtime.agentMetadata === 'toml') {
273
- writeCodexAgentToml(srcFile, agentsDest);
274
- }
275
- }
276
-
277
- function removeSkillEntry(skillsDir, entry) {
278
- const entryPath = path.join(skillsDir, entry.name);
279
- if (entry.isDirectory()) {
280
- const skillFile = path.join(entryPath, 'SKILL.md');
281
- if (entry.name.startsWith('god-') || entry.name === 'god' || entry.name === 'godpowers') {
282
- if (fs.existsSync(skillFile)) {
283
- fs.rmSync(entryPath, { recursive: true, force: true });
284
- return true;
285
- }
286
- }
287
- return false;
288
- }
289
- if (entry.name.startsWith('god-') || entry.name === 'god.md' || entry.name === 'godpowers.md') {
290
- fs.unlinkSync(entryPath);
291
- return true;
292
- }
293
- return false;
294
- }
295
-
296
- // ---------------------------------------------------------------------------
297
- // Parse args
298
- // ---------------------------------------------------------------------------
299
-
300
- function parseArgs(argv) {
301
- const args = argv.slice(2);
302
- const opts = {
303
- command: null,
304
- project: process.cwd(),
305
- json: false,
306
- brief: false,
307
- extensionName: null,
308
- extensionOutput: process.cwd(),
309
- extensionSkill: null,
310
- extensionAgent: null,
311
- extensionWorkflow: null,
312
- runtimes: [],
313
- global: false,
314
- local: false,
315
- all: false,
316
- help: false,
317
- uninstall: false,
318
- };
319
-
320
- for (let i = 0; i < args.length; i++) {
321
- const arg = args[i];
322
- switch (arg) {
323
- case 'status':
324
- case 'next':
325
- case 'quick-proof':
326
- case 'automation-status':
327
- case 'automation-setup':
328
- case 'dogfood':
329
- case 'extension-scaffold':
330
- opts.command = arg;
331
- break;
332
- case '--json':
333
- opts.json = true;
334
- break;
335
- case '--brief':
336
- opts.brief = true;
337
- break;
338
- case '--project':
339
- if (args[i + 1]) {
340
- opts.project = path.resolve(args[i + 1]);
341
- i++;
342
- }
343
- break;
344
- case '-g':
345
- case '--global':
346
- opts.global = true;
347
- break;
348
- case '-l':
349
- case '--local':
350
- opts.local = true;
351
- break;
352
- case '--all':
353
- opts.all = true;
354
- break;
355
- case '-h':
356
- case '--help':
357
- opts.help = true;
358
- break;
359
- case '-u':
360
- case '--uninstall':
361
- opts.uninstall = true;
362
- break;
363
- default:
364
- if (arg.startsWith('--project=')) {
365
- opts.project = path.resolve(arg.slice('--project='.length));
366
- } else if (arg.startsWith('--name=')) {
367
- opts.extensionName = arg.slice('--name='.length);
368
- } else if (arg.startsWith('--output=')) {
369
- opts.extensionOutput = path.resolve(arg.slice('--output='.length));
370
- } else if (arg.startsWith('--skill=')) {
371
- opts.extensionSkill = arg.slice('--skill='.length);
372
- } else if (arg.startsWith('--agent=')) {
373
- opts.extensionAgent = arg.slice('--agent='.length);
374
- } else if (arg.startsWith('--workflow=')) {
375
- opts.extensionWorkflow = arg.slice('--workflow='.length);
376
- } else if (arg.startsWith('--') && RUNTIMES[arg.slice(2)]) {
377
- opts.runtimes.push(arg.slice(2));
378
- }
379
- break;
380
- }
381
- }
382
-
383
- return opts;
384
- }
385
-
386
- // ---------------------------------------------------------------------------
387
- // Install
388
- // ---------------------------------------------------------------------------
389
-
390
- function installForRuntime(runtimeKey, srcDir, opts = {}) {
391
- const runtime = resolveRuntime(runtimeKey, opts);
392
- if (!runtime) {
393
- error(`Unknown runtime: ${runtimeKey}`);
394
- return false;
395
- }
396
-
397
- log(`\n Installing for \x1b[36m${runtime.name}\x1b[0m to \x1b[36m${runtime.configDir}\x1b[0m\n`);
398
-
399
- ensureDir(runtime.configDir);
400
-
401
- // 1. Install slash command skills to skills/
402
- // Each skill in skills/ becomes a slash command (e.g. /god-mode)
403
- const skillsSrc = path.join(srcDir, 'skills');
404
- const skillsDest = path.join(runtime.configDir, runtime.skillsDir);
405
- if (fs.existsSync(skillsSrc)) {
406
- ensureDir(skillsDest);
407
- let count = 0;
408
- for (const file of fs.readdirSync(skillsSrc)) {
409
- if (file.endsWith('.md')) {
410
- installSkillFile(path.join(skillsSrc, file), skillsDest, runtimeKey);
411
- count++;
412
- }
413
- }
414
- const shape = runtimeKey === 'codex' ? 'Codex skill directories' : 'skills/';
415
- success(`Installed ${count} slash commands to ${shape}`);
416
- }
417
-
418
- // 2. Install specialist agents to agents/
419
- // Each agent is spawnable from a skill (e.g., god-pm, god-architect, god-executor)
420
- const agentsSrc = path.join(srcDir, 'agents');
421
- const agentsDest = path.join(runtime.configDir, 'agents');
422
- if (fs.existsSync(agentsSrc)) {
423
- ensureDir(agentsDest);
424
- let count = 0;
425
- for (const file of fs.readdirSync(agentsSrc)) {
426
- if (/^god-.*\.md$/.test(file)) {
427
- const srcFile = path.join(agentsSrc, file);
428
- installAgentFile(srcFile, agentsDest, runtime);
429
- count++;
430
- }
431
- }
432
- const shape = runtime.agentMetadata === 'toml' ? 'agents/ with Codex metadata' : 'agents/';
433
- success(`Installed ${count} specialist agents to ${shape}`);
434
- }
435
-
436
- // 3. Install the master SKILL.md (always-on context)
437
- const masterSkill = path.join(srcDir, 'SKILL.md');
438
- if (fs.existsSync(masterSkill)) {
439
- installSkillFile(masterSkill, skillsDest, runtimeKey, 'godpowers');
440
- success('Installed master SKILL.md as godpowers');
441
- }
442
-
443
- // 4. Install templates
444
- const templatesSrc = path.join(srcDir, 'templates');
445
- if (fs.existsSync(templatesSrc)) {
446
- const templatesDest = path.join(runtime.configDir, 'godpowers-templates');
447
- copyRecursive(templatesSrc, templatesDest);
448
- success('Installed templates/');
449
- }
450
-
451
- // 4b. Install references (HAVE-NOTS catalog and per-tier antipatterns)
452
- const referencesSrc = path.join(srcDir, 'references');
453
- if (fs.existsSync(referencesSrc)) {
454
- const referencesDest = path.join(runtime.configDir, 'godpowers-references');
455
- copyRecursive(referencesSrc, referencesDest);
456
- success('Installed references/');
457
- }
458
-
459
- // 4c. Install workflows (declarative YAML for /god-mode and friends)
460
- const workflowsSrc = path.join(srcDir, 'workflows');
461
- if (fs.existsSync(workflowsSrc)) {
462
- const workflowsDest = path.join(runtime.configDir, 'godpowers-workflows');
463
- copyRecursive(workflowsSrc, workflowsDest);
464
- success('Installed workflows/');
465
- }
466
-
467
- // 4d. Install schemas (for validation)
468
- const schemaSrc = path.join(srcDir, 'schema');
469
- if (fs.existsSync(schemaSrc)) {
470
- const schemaDest = path.join(runtime.configDir, 'godpowers-schema');
471
- copyRecursive(schemaSrc, schemaDest);
472
- success('Installed schema/');
473
- }
474
-
475
- // 4e. Install routing definitions (per-command prerequisites + next-suggestions)
476
- const routingSrc = path.join(srcDir, 'routing');
477
- if (fs.existsSync(routingSrc)) {
478
- const routingDest = path.join(runtime.configDir, 'godpowers-routing');
479
- copyRecursive(routingSrc, routingDest);
480
- success('Installed routing/');
481
- }
482
-
483
- // 4f. Install the executable runtime bundle with lib/ next to its data dirs.
484
- const runtimeBundleDest = path.join(runtime.configDir, 'godpowers-runtime');
485
- copyRuntimeBundle(srcDir, runtimeBundleDest);
486
- success('Installed runtime bundle/');
487
-
488
- // 5. Install hooks (Claude Code only for now)
489
- if (runtimeKey === 'claude') {
490
- const hooksSrc = path.join(srcDir, 'hooks');
491
- const hooksDest = path.join(runtime.configDir, 'hooks');
492
- if (fs.existsSync(hooksSrc)) {
493
- ensureDir(hooksDest);
494
- for (const file of fs.readdirSync(hooksSrc)) {
495
- const src = path.join(hooksSrc, file);
496
- const dest = path.join(hooksDest, file);
497
- fs.copyFileSync(src, dest);
498
- try { fs.chmodSync(dest, 0o755); } catch (e) {}
499
- }
500
- success('Installed hooks/');
501
- }
502
- }
503
-
504
- // 6. Write VERSION
505
- fs.writeFileSync(path.join(runtime.configDir, 'GODPOWERS_VERSION'), VERSION);
506
- success(`Wrote GODPOWERS_VERSION (${VERSION})`);
507
-
508
- return true;
509
- }
510
-
511
- // ---------------------------------------------------------------------------
512
- // Uninstall
513
- // ---------------------------------------------------------------------------
514
-
515
- function uninstallForRuntime(runtimeKey, opts = {}) {
516
- const runtime = resolveRuntime(runtimeKey, opts);
517
- if (!runtime) {
518
- error(`Unknown runtime: ${runtimeKey}`);
519
- return false;
520
- }
521
-
522
- log(`\n Uninstalling from \x1b[36m${runtime.name}\x1b[0m at \x1b[36m${runtime.configDir}\x1b[0m\n`);
523
-
524
- const skillsDir = path.join(runtime.configDir, runtime.skillsDir);
525
- const agentsDir = path.join(runtime.configDir, 'agents');
526
- const templatesDir = path.join(runtime.configDir, 'godpowers-templates');
527
- const referencesDir = path.join(runtime.configDir, 'godpowers-references');
528
- const workflowsDir = path.join(runtime.configDir, 'godpowers-workflows');
529
- const schemaDir = path.join(runtime.configDir, 'godpowers-schema');
530
- const routingDir = path.join(runtime.configDir, 'godpowers-routing');
531
- const runtimeBundleDir = path.join(runtime.configDir, 'godpowers-runtime');
532
- const versionFile = path.join(runtime.configDir, 'GODPOWERS_VERSION');
533
-
534
- let removed = 0;
535
-
536
- // Remove all god-* skills
537
- if (fs.existsSync(skillsDir)) {
538
- for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
539
- if (removeSkillEntry(skillsDir, entry)) {
540
- removed++;
541
- }
542
- }
543
- success(`Removed ${removed} god-* skill(s)`);
544
- }
545
-
546
- // Remove all god-* agents
547
- let agentsRemoved = 0;
548
- if (fs.existsSync(agentsDir)) {
549
- for (const file of fs.readdirSync(agentsDir)) {
550
- if (file.startsWith('god-')) {
551
- fs.unlinkSync(path.join(agentsDir, file));
552
- agentsRemoved++;
553
- }
554
- }
555
- success(`Removed ${agentsRemoved} god-* agent(s)`);
556
- }
557
-
558
- // Remove installed data and runtime directories
559
- for (const dir of [
560
- templatesDir,
561
- referencesDir,
562
- workflowsDir,
563
- schemaDir,
564
- routingDir,
565
- runtimeBundleDir
566
- ]) {
567
- if (fs.existsSync(dir)) {
568
- fs.rmSync(dir, { recursive: true, force: true });
569
- success(`Removed ${path.basename(dir)}/`);
570
- }
571
- }
572
-
573
- // Remove hooks (Claude Code only)
574
- if (runtimeKey === 'claude') {
575
- const hooksDir = path.join(runtime.configDir, 'hooks');
576
- for (const hook of ['session-start.sh', 'pre-tool-use.sh']) {
577
- const hookPath = path.join(hooksDir, hook);
578
- if (fs.existsSync(hookPath)) {
579
- fs.unlinkSync(hookPath);
580
- success(`Removed hooks/${hook}`);
581
- }
582
- }
583
- }
584
-
585
- if (fs.existsSync(versionFile)) {
586
- fs.unlinkSync(versionFile);
587
- }
588
-
589
- return true;
590
- }
591
-
592
44
  function showHelp() {
593
45
  console.log(BANNER);
594
46
  log('Usage: npx godpowers [command] [options]\n');
@@ -610,8 +62,8 @@ function showHelp() {
610
62
  log(' --skill=<name> Extension skill name for extension-scaffold');
611
63
  log(' --agent=<name> Extension agent name for extension-scaffold');
612
64
  log(' --workflow=<name> Extension workflow name for extension-scaffold');
613
- log(' -g, --global Install globally (to config directory)');
614
- log(' -l, --local Install locally (to current directory)');
65
+ log(' -g, --global Install globally to the config directory');
66
+ log(' -l, --local Install locally to the current directory');
615
67
  log(' --claude Install for Claude Code');
616
68
  log(' --codex Install for Codex');
617
69
  log(' --cursor Install for Cursor');
@@ -663,13 +115,14 @@ function runDashboardCommand(opts) {
663
115
  const result = dashboard.compute(opts.project);
664
116
  if (opts.json) {
665
117
  console.log(JSON.stringify(result, null, 2));
666
- } else {
667
- console.log(dashboard.render(result, { brief: opts.brief }));
668
- if (opts.command === 'next') {
669
- console.log('');
670
- console.log('Suggested next command:');
671
- console.log(` ${result.next && result.next.command ? result.next.command : 'describe the next intent'}`);
672
- }
118
+ return;
119
+ }
120
+
121
+ console.log(dashboard.render(result, { brief: opts.brief }));
122
+ if (opts.command === 'next') {
123
+ console.log('');
124
+ console.log('Suggested next command:');
125
+ console.log(` ${result.next && result.next.command ? result.next.command : 'describe the next intent'}`);
673
126
  }
674
127
  }
675
128
 
@@ -709,95 +162,75 @@ function runExtensionScaffoldCommand(opts) {
709
162
  });
710
163
  if (opts.json) {
711
164
  console.log(JSON.stringify(result, null, 2));
712
- } else {
713
- success(`Scaffolded ${result.name} at ${result.path}`);
714
- if (result.written.length > 0) {
715
- log(`Wrote ${result.written.length} file(s): ${result.written.join(', ')}`);
716
- }
717
- if (result.validation.length > 0) {
718
- warn(`Validation warnings: ${result.validation.join('; ')}`);
719
- } else {
720
- success('Extension manifest validates');
721
- }
165
+ return;
722
166
  }
723
- }
724
-
725
- // ---------------------------------------------------------------------------
726
- // Main
727
- // ---------------------------------------------------------------------------
728
167
 
729
- function main() {
730
- const opts = parseArgs(process.argv);
731
-
732
- if (opts.help) {
733
- showHelp();
734
- process.exit(0);
168
+ success(`Scaffolded ${result.name} at ${result.path}`);
169
+ if (result.written.length > 0) {
170
+ log(`Wrote ${result.written.length} file(s): ${result.written.join(', ')}`);
735
171
  }
172
+ if (result.validation.length > 0) {
173
+ warn(`Validation warnings: ${result.validation.join('; ')}`);
174
+ } else {
175
+ success('Extension manifest validates');
176
+ }
177
+ }
736
178
 
179
+ function runCommand(opts) {
737
180
  if (opts.command === 'status' || opts.command === 'next') {
738
181
  runDashboardCommand(opts);
739
- return;
182
+ return true;
740
183
  }
741
-
742
184
  if (opts.command === 'quick-proof') {
743
185
  runQuickProofCommand(opts);
744
- return;
186
+ return true;
745
187
  }
746
-
747
188
  if (opts.command === 'automation-status' || opts.command === 'automation-setup') {
748
189
  runAutomationCommand(opts);
749
- return;
190
+ return true;
750
191
  }
751
-
752
192
  if (opts.command === 'dogfood') {
753
193
  runDogfoodCommand(opts);
754
- return;
194
+ return true;
755
195
  }
756
-
757
196
  if (opts.command === 'extension-scaffold') {
758
197
  runExtensionScaffoldCommand(opts);
759
- return;
760
- }
761
-
762
- console.log(BANNER);
763
-
764
- const srcDir = path.resolve(__dirname, '..');
765
-
766
- // Detect non-interactive and default to Claude Code.
767
- if (opts.runtimes.length === 0 && !opts.all) {
768
- if (!process.stdin.isTTY) {
769
- warn('Non-interactive terminal detected, defaulting to Claude Code install');
770
- opts.runtimes = ['claude'];
771
- if (!opts.local) opts.global = true;
772
- } else {
773
- // Interactive mode: default to claude
774
- opts.runtimes = ['claude'];
775
- if (!opts.local) opts.global = true;
776
- }
198
+ return true;
777
199
  }
200
+ return false;
201
+ }
778
202
 
779
- if (opts.all) {
780
- opts.runtimes = Object.keys(RUNTIMES);
203
+ function applyDefaultRuntimeSelection(opts) {
204
+ if (opts.runtimes.length > 0 || opts.all) return;
205
+ if (!process.stdin.isTTY) {
206
+ // Refuse to perform a silent global install when there is no human to
207
+ // confirm and no explicit target was given (CI, piped, some npx contexts).
208
+ warn('No runtime selected and stdin is not a TTY.');
209
+ warn('Refusing a silent global install. Re-run with an explicit target,');
210
+ warn('for example: npx godpowers --claude --global (or --all).');
211
+ process.exit(1);
781
212
  }
213
+ opts.runtimes = ['claude'];
214
+ if (!opts.local) opts.global = true;
215
+ }
782
216
 
783
- // Handle uninstall
784
- if (opts.uninstall) {
785
- let removed = 0;
786
- for (const runtime of opts.runtimes) {
787
- if (uninstallForRuntime(runtime, opts)) {
788
- removed++;
789
- }
790
- }
791
- log('');
792
- if (removed > 0) {
793
- log(`\x1b[32mUninstalled\x1b[0m from ${removed} runtime(s).`);
794
- } else {
795
- warn('No runtimes uninstalled.');
217
+ function runUninstall(opts) {
218
+ let removed = 0;
219
+ for (const runtime of opts.runtimes) {
220
+ if (uninstallForRuntime(runtime, opts)) {
221
+ removed++;
796
222
  }
797
- log('');
798
- return;
799
223
  }
224
+ log('');
225
+ if (removed > 0) {
226
+ log(`\x1b[32mUninstalled\x1b[0m from ${removed} runtime(s).`);
227
+ } else {
228
+ warn('No runtimes uninstalled.');
229
+ }
230
+ log('');
231
+ }
800
232
 
233
+ function runInstall(opts, srcDir) {
801
234
  let installed = 0;
802
235
  for (const runtime of opts.runtimes) {
803
236
  if (installForRuntime(runtime, srcDir, opts)) {
@@ -805,31 +238,62 @@ function main() {
805
238
  }
806
239
  }
807
240
 
808
- if (installed > 0) {
809
- // Count slash commands for verification message
810
- const skillsCount = fs.readdirSync(path.join(srcDir, 'skills')).filter(f => f.endsWith('.md')).length;
811
- const agentsCount = fs.readdirSync(path.join(srcDir, 'agents')).filter(f => /^god-.*\.md$/.test(f)).length;
812
-
813
- log('');
814
- log(`\x1b[32mDone!\x1b[0m Installed Godpowers v${VERSION} for ${installed} runtime(s).`);
815
- log('');
816
- log(`\x1b[36mInstalled:\x1b[0m`);
817
- log(` ${skillsCount} slash commands (try: /god-mode, /god-next, /god-status)`);
818
- log(` ${agentsCount} specialist agents`);
819
- log(` Templates and references for artifact discipline`);
820
- log('');
821
- log(`\x1b[36mNext steps:\x1b[0m`);
822
- log(` 1. Open your AI coding tool in any project directory`);
823
- log(` 2. Type: \x1b[36m/god-mode\x1b[0m for the full autonomous project run`);
824
- log(` Or: \x1b[36m/god-next\x1b[0m to see what to run next`);
825
- log(` Or: \x1b[36m/god-init\x1b[0m to start a new project`);
826
- log('');
827
- log(`\x1b[36mDocs:\x1b[0m https://github.com/godpowers/godpowers`);
828
- log('');
829
- } else {
241
+ if (installed === 0) {
830
242
  error('No runtimes installed. Run with --help for usage.');
831
243
  process.exit(1);
832
244
  }
245
+
246
+ const surface = countInstalledSurface(srcDir);
247
+ log('');
248
+ log(`\x1b[32mDone!\x1b[0m Installed Godpowers v${VERSION} for ${installed} runtime(s).`);
249
+ log('');
250
+ log(`\x1b[36mInstalled:\x1b[0m`);
251
+ log(` ${surface.skills} slash commands (try: /god-mode, /god-next, /god-status)`);
252
+ log(` ${surface.agents} specialist agents`);
253
+ log(' Templates and references for artifact discipline');
254
+ log('');
255
+ log(`\x1b[36mNext steps:\x1b[0m`);
256
+ log(' 1. Open your AI coding tool in any project directory');
257
+ log(` 2. Type: \x1b[36m/god-mode\x1b[0m for the full autonomous project run`);
258
+ log(` Or: \x1b[36m/god-next\x1b[0m to see what to run next`);
259
+ log(` Or: \x1b[36m/god-init\x1b[0m to start a new project`);
260
+ log('');
261
+ log(`\x1b[36mDocs:\x1b[0m https://github.com/godpowers/godpowers`);
262
+ log('');
263
+ }
264
+
265
+ function main() {
266
+ const opts = parseArgs(process.argv);
267
+
268
+ if (opts.help) {
269
+ showHelp();
270
+ process.exit(0);
271
+ }
272
+
273
+ if (runCommand(opts)) return;
274
+
275
+ console.log(BANNER);
276
+ const srcDir = path.resolve(__dirname, '..');
277
+
278
+ applyDefaultRuntimeSelection(opts);
279
+ if (opts.all) {
280
+ opts.runtimes = runtimeKeys();
281
+ }
282
+
283
+ if (opts.uninstall) {
284
+ runUninstall(opts);
285
+ } else {
286
+ runInstall(opts, srcDir);
287
+ }
833
288
  }
834
289
 
835
290
  main();
291
+
292
+ module.exports = {
293
+ showHelp,
294
+ runCommand,
295
+ applyDefaultRuntimeSelection,
296
+ runInstall,
297
+ runUninstall,
298
+ RUNTIMES
299
+ };