popilot 0.6.0 → 0.7.0

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 (112) hide show
  1. package/bin/cli.mjs +204 -2
  2. package/lib/doctor.mjs +38 -1
  3. package/lib/hydrate.mjs +15 -0
  4. package/lib/scaffold.mjs +5 -0
  5. package/lib/setup-wizard.mjs +35 -2
  6. package/package.json +1 -1
  7. package/scaffold/.context/project.yaml.example +19 -0
  8. package/scaffold/mcp-pm/package.json +19 -0
  9. package/scaffold/mcp-pm/src/api-client.ts +69 -0
  10. package/scaffold/mcp-pm/src/index.ts +660 -0
  11. package/scaffold/mcp-pm/tsconfig.json +14 -0
  12. package/scaffold/pm-api/package.json +21 -0
  13. package/scaffold/pm-api/sql/schema-core.sql +331 -0
  14. package/scaffold/pm-api/sql/schema-docs.sql +25 -0
  15. package/scaffold/pm-api/sql/schema-meetings.sql +17 -0
  16. package/scaffold/pm-api/sql/schema-rewards.sql +16 -0
  17. package/scaffold/pm-api/src/auth.ts +28 -0
  18. package/scaffold/pm-api/src/blockchain/adapter.ts +20 -0
  19. package/scaffold/pm-api/src/blockchain/tron.ts +62 -0
  20. package/scaffold/pm-api/src/db/adapter.ts +36 -0
  21. package/scaffold/pm-api/src/db/turso.ts +147 -0
  22. package/scaffold/pm-api/src/index.ts +114 -0
  23. package/scaffold/pm-api/src/mcp-tools/dashboard.ts +40 -0
  24. package/scaffold/pm-api/src/mcp-tools/epic.ts +67 -0
  25. package/scaffold/pm-api/src/mcp-tools/event.ts +89 -0
  26. package/scaffold/pm-api/src/mcp-tools/index.ts +11 -0
  27. package/scaffold/pm-api/src/mcp-tools/initiative.ts +51 -0
  28. package/scaffold/pm-api/src/mcp-tools/memo.ts +164 -0
  29. package/scaffold/pm-api/src/mcp-tools/notification.ts +37 -0
  30. package/scaffold/pm-api/src/mcp-tools/retro.ts +183 -0
  31. package/scaffold/pm-api/src/mcp-tools/sprint.ts +204 -0
  32. package/scaffold/pm-api/src/mcp-tools/standup.ts +136 -0
  33. package/scaffold/pm-api/src/mcp-tools/story.ts +230 -0
  34. package/scaffold/pm-api/src/mcp-tools/task.ts +187 -0
  35. package/scaffold/pm-api/src/mcp-tools/utils.ts +83 -0
  36. package/scaffold/pm-api/src/mcp.ts +871 -0
  37. package/scaffold/pm-api/src/nudge.ts +283 -0
  38. package/scaffold/pm-api/src/routes/auth.ts +32 -0
  39. package/scaffold/pm-api/src/routes/v2-activity.ts +27 -0
  40. package/scaffold/pm-api/src/routes/v2-admin.ts +165 -0
  41. package/scaffold/pm-api/src/routes/v2-dashboard.ts +189 -0
  42. package/scaffold/pm-api/src/routes/v2-docs.ts +34 -0
  43. package/scaffold/pm-api/src/routes/v2-initiatives.ts +118 -0
  44. package/scaffold/pm-api/src/routes/v2-kickoff.ts +265 -0
  45. package/scaffold/pm-api/src/routes/v2-meetings.ts +324 -0
  46. package/scaffold/pm-api/src/routes/v2-memos.ts +257 -0
  47. package/scaffold/pm-api/src/routes/v2-nav.ts +260 -0
  48. package/scaffold/pm-api/src/routes/v2-notifications.ts +79 -0
  49. package/scaffold/pm-api/src/routes/v2-page-content.ts +35 -0
  50. package/scaffold/pm-api/src/routes/v2-pm.ts +380 -0
  51. package/scaffold/pm-api/src/routes/v2-policy.ts +58 -0
  52. package/scaffold/pm-api/src/routes/v2-retro.ts +221 -0
  53. package/scaffold/pm-api/src/routes/v2-rewards.ts +132 -0
  54. package/scaffold/pm-api/src/routes/v2-scenarios.ts +48 -0
  55. package/scaffold/pm-api/src/routes/v2-search.ts +32 -0
  56. package/scaffold/pm-api/src/routes/v2-standup.ts +127 -0
  57. package/scaffold/pm-api/src/routes/v2-user.ts +38 -0
  58. package/scaffold/pm-api/src/types.ts +11 -0
  59. package/scaffold/pm-api/src/utils/activity.ts +22 -0
  60. package/scaffold/pm-api/src/utils/admin.ts +9 -0
  61. package/scaffold/pm-api/src/utils/agent-notify.ts +62 -0
  62. package/scaffold/pm-api/src/utils/assignee.ts +69 -0
  63. package/scaffold/pm-api/src/utils/db.ts +45 -0
  64. package/scaffold/pm-api/src/utils/initiative.ts +23 -0
  65. package/scaffold/pm-api/src/utils/sprint-lifecycle.ts +96 -0
  66. package/scaffold/pm-api/tsconfig.json +15 -0
  67. package/scaffold/pm-api/wrangler.toml.hbs +11 -0
  68. package/scaffold/spec-site/package-lock.json +40 -0
  69. package/scaffold/spec-site/package.json +4 -1
  70. package/scaffold/spec-site/src/api/types.ts +6 -0
  71. package/scaffold/spec-site/src/components/AppHeader.vue +429 -55
  72. package/scaffold/spec-site/src/components/MemberSelect.vue +48 -0
  73. package/scaffold/spec-site/src/components/NotificationDropdown.vue +116 -0
  74. package/scaffold/spec-site/src/components/SearchModal.vue +102 -0
  75. package/scaffold/spec-site/src/components/VelocityChart.vue +77 -0
  76. package/scaffold/spec-site/src/composables/pmTypes.ts +15 -2
  77. package/scaffold/spec-site/src/composables/useDashboard.ts +221 -0
  78. package/scaffold/spec-site/src/composables/useMediaQuery.ts +28 -0
  79. package/scaffold/spec-site/src/composables/useNotification.ts +200 -0
  80. package/scaffold/spec-site/src/composables/usePmStore.ts +48 -1
  81. package/scaffold/spec-site/src/composables/useRetro.ts +6 -0
  82. package/scaffold/spec-site/src/composables/useStandup.ts +201 -0
  83. package/scaffold/spec-site/src/composables/useTheme.ts +37 -0
  84. package/scaffold/spec-site/src/composables/useUser.ts +19 -1
  85. package/scaffold/spec-site/src/features.ts +108 -0
  86. package/scaffold/spec-site/src/pages/AdminPage.vue +299 -0
  87. package/scaffold/spec-site/src/pages/DashboardPage.vue +650 -0
  88. package/scaffold/spec-site/src/pages/DocsHub.vue +157 -0
  89. package/scaffold/spec-site/src/pages/InboxPage.vue +156 -0
  90. package/scaffold/spec-site/src/pages/MeetingsPage.vue +294 -0
  91. package/scaffold/spec-site/src/pages/MyPage.vue +343 -0
  92. package/scaffold/spec-site/src/pages/RewardsPage.vue +266 -0
  93. package/scaffold/spec-site/src/pages/board/BoardAdmin.vue +422 -0
  94. package/scaffold/spec-site/src/pages/board/BoardEpicSection.vue +54 -0
  95. package/scaffold/spec-site/src/pages/board/BoardPage.vue +884 -0
  96. package/scaffold/spec-site/src/pages/board/BoardStoryCard.vue +67 -0
  97. package/scaffold/spec-site/src/pages/board/BoardTaskItem.vue +52 -0
  98. package/scaffold/spec-site/src/pages/board/MyTasksPage.vue +202 -0
  99. package/scaffold/spec-site/src/pages/board/SprintClose.vue +167 -0
  100. package/scaffold/spec-site/src/pages/board/SprintColumn.vue +49 -0
  101. package/scaffold/spec-site/src/pages/board/SprintKickoff.vue +389 -0
  102. package/scaffold/spec-site/src/pages/board/StatusBadge.vue +52 -0
  103. package/scaffold/spec-site/src/pages/board/StoryDetailPanel.vue +495 -0
  104. package/scaffold/spec-site/src/pages/board/TaskCard.vue +42 -0
  105. package/scaffold/spec-site/src/pages/retro/RetroCard.vue +36 -2
  106. package/scaffold/spec-site/src/pages/retro/RetroHeader.vue +82 -66
  107. package/scaffold/spec-site/src/pages/retro/RetroPage.vue +47 -18
  108. package/scaffold/spec-site/src/pages/standup/StandupEntryCard.vue +551 -0
  109. package/scaffold/spec-site/src/pages/standup/StandupForm.vue +68 -0
  110. package/scaffold/spec-site/src/pages/standup/StandupList.vue +71 -0
  111. package/scaffold/spec-site/src/pages/standup/StandupPage.vue +225 -0
  112. package/scaffold/spec-site/src/router.ts +141 -0
package/bin/cli.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { resolve, basename } from 'node:path';
4
+ import { readFileSync, existsSync } from 'node:fs';
4
5
  import { execSync } from 'node:child_process';
5
6
  import { copyScaffold, appendToFile, detectExisting } from '../lib/scaffold.mjs';
6
7
  import { runSetupWizard } from '../lib/setup-wizard.mjs';
@@ -15,6 +16,8 @@ const USAGE = `
15
16
  init [dir] Scaffold + interactive setup + hydration (default)
16
17
  hydrate [dir] Sync latest scaffold templates + re-hydrate from project.yaml
17
18
  doctor [dir] Check installation health
19
+ deploy [dir] Deploy pm-api to Cloudflare Workers (Tier 2)
20
+ migrate [dir] Run SQL schema migrations on pm-api database (Tier 2)
18
21
  help Show this help
19
22
 
20
23
  Options:
@@ -28,10 +31,12 @@ const USAGE = `
28
31
  npx popilot hydrate
29
32
  npx popilot hydrate --force
30
33
  npx popilot doctor
34
+ npx popilot deploy
35
+ npx popilot migrate
31
36
  npx popilot my-project # same as: popilot init my-project
32
37
  `;
33
38
 
34
- const SUBCOMMANDS = new Set(['init', 'hydrate', 'doctor', 'help']);
39
+ const SUBCOMMANDS = new Set(['init', 'hydrate', 'doctor', 'deploy', 'migrate', 'help']);
35
40
 
36
41
  async function main() {
37
42
  const args = process.argv.slice(2);
@@ -71,6 +76,12 @@ async function main() {
71
76
  case 'doctor':
72
77
  await cmdDoctor(targetDir, { skipSpecSite, platform });
73
78
  break;
79
+ case 'deploy':
80
+ await cmdDeploy(targetDir);
81
+ break;
82
+ case 'migrate':
83
+ await cmdMigrate(targetDir);
84
+ break;
74
85
  }
75
86
  }
76
87
 
@@ -133,7 +144,34 @@ async function cmdInit(targetDir, { skipSpecSite, force, platform }) {
133
144
  console.log(` ${f} ✅ (domain)`);
134
145
  }
135
146
 
136
- // 4. Install spec-site dependencies
147
+ // 4a. Install pm-api dependencies (Tier 2)
148
+ const pmApiDir = resolve(targetDir, 'pm-api');
149
+ try {
150
+ const { access: fsAccess } = await import('node:fs/promises');
151
+ await fsAccess(resolve(pmApiDir, 'package.json'));
152
+ console.log();
153
+ console.log(' 📦 Installing pm-api dependencies...');
154
+ try {
155
+ execSync('npm install', { cwd: pmApiDir, stdio: 'pipe' });
156
+ console.log(' ✅ Done');
157
+ } catch {
158
+ console.log(' ⚠️ npm install failed. Run manually: cd pm-api && npm install');
159
+ }
160
+
161
+ // Also install mcp-pm
162
+ const mcpPmDir = resolve(targetDir, 'mcp-pm');
163
+ console.log(' 📦 Installing mcp-pm dependencies...');
164
+ try {
165
+ execSync('npm install', { cwd: mcpPmDir, stdio: 'pipe' });
166
+ console.log(' ✅ Done');
167
+ } catch {
168
+ console.log(' ⚠️ npm install failed. Run manually: cd mcp-pm && npm install');
169
+ }
170
+ } catch {
171
+ // pm-api not present (Tier 0/1) — skip
172
+ }
173
+
174
+ // 4b. Install spec-site dependencies
137
175
  if (!skipSpecSite) {
138
176
  const specSiteDir = resolve(targetDir, 'spec-site');
139
177
  console.log();
@@ -241,6 +279,170 @@ async function cmdDoctor(targetDir, { skipSpecSite, platform }) {
241
279
  process.exit(failed.length > 0 ? 1 : 0);
242
280
  }
243
281
 
282
+ // ── deploy ───────────────────────────────────────────────
283
+
284
+ async function cmdDeploy(targetDir) {
285
+ console.log();
286
+ console.log(' 🚀 Popilot — Deploy pm-api');
287
+ console.log(' ══════════════════════════════════════');
288
+ console.log();
289
+
290
+ const pmApiDir = resolve(targetDir, 'pm-api');
291
+
292
+ if (!existsSync(pmApiDir)) {
293
+ console.log(' ❌ pm-api directory not found.');
294
+ console.log(` Expected at: ${pmApiDir}`);
295
+ console.log(' Deploy is only available for Tier 2 (interactive spec-site with backend).');
296
+ console.log();
297
+ process.exit(1);
298
+ }
299
+
300
+ const wranglerToml = resolve(pmApiDir, 'wrangler.toml');
301
+ if (!existsSync(wranglerToml)) {
302
+ console.log(' ❌ wrangler.toml not found in pm-api.');
303
+ console.log(' Found wrangler.toml.hbs — run `popilot hydrate` first to generate wrangler.toml.');
304
+ console.log();
305
+ process.exit(1);
306
+ }
307
+
308
+ console.log(` 📂 Deploying from: ${pmApiDir}`);
309
+ console.log();
310
+
311
+ try {
312
+ execSync('npx wrangler deploy', { cwd: pmApiDir, stdio: 'inherit' });
313
+ console.log();
314
+ console.log(' ✅ pm-api deployed successfully.');
315
+ console.log();
316
+ } catch {
317
+ console.log();
318
+ console.log(' ❌ Deploy failed. Check the wrangler output above for details.');
319
+ console.log();
320
+ process.exit(1);
321
+ }
322
+ }
323
+
324
+ // ── migrate ──────────────────────────────────────────────
325
+
326
+ async function cmdMigrate(targetDir) {
327
+ console.log();
328
+ console.log(' 🗄️ Popilot — Run SQL Migrations');
329
+ console.log(' ══════════════════════════════════════');
330
+ console.log();
331
+
332
+ const pmApiDir = resolve(targetDir, 'pm-api');
333
+
334
+ if (!existsSync(pmApiDir)) {
335
+ console.log(' ❌ pm-api directory not found.');
336
+ console.log(` Expected at: ${pmApiDir}`);
337
+ console.log(' Migrate is only available for Tier 2 (interactive spec-site with backend).');
338
+ console.log();
339
+ process.exit(1);
340
+ }
341
+
342
+ const sqlDir = resolve(pmApiDir, 'sql');
343
+ if (!existsSync(sqlDir)) {
344
+ console.log(' ❌ pm-api/sql/ directory not found.');
345
+ console.log(' No migration files available.');
346
+ console.log();
347
+ process.exit(1);
348
+ }
349
+
350
+ // Read D1 database name from wrangler.toml
351
+ const wranglerToml = resolve(pmApiDir, 'wrangler.toml');
352
+ if (!existsSync(wranglerToml)) {
353
+ console.log(' ❌ wrangler.toml not found in pm-api.');
354
+ console.log(' Run `popilot hydrate` first to generate wrangler.toml.');
355
+ console.log();
356
+ process.exit(1);
357
+ }
358
+
359
+ const wranglerContent = readFileSync(wranglerToml, 'utf-8');
360
+ const dbNameMatch = wranglerContent.match(/database_name\s*=\s*"([^"]+)"/);
361
+ const dbName = dbNameMatch ? dbNameMatch[1] : null;
362
+
363
+ if (!dbName) {
364
+ console.log(' ❌ Could not find D1 database_name in wrangler.toml.');
365
+ console.log(' Ensure [[d1_databases]] is configured with a database_name.');
366
+ console.log();
367
+ process.exit(1);
368
+ }
369
+
370
+ console.log(` 📂 SQL directory: ${sqlDir}`);
371
+ console.log(` 🗃️ D1 database: ${dbName}`);
372
+ console.log();
373
+
374
+ // Read project.yaml to determine enabled features
375
+ const projectYaml = resolve(targetDir, '.context', 'project.yaml');
376
+ let features = { rewards: false, meetings: true, docs: true };
377
+
378
+ if (existsSync(projectYaml)) {
379
+ try {
380
+ const yamlContent = readFileSync(projectYaml, 'utf-8');
381
+ // Parse feature flags from YAML (simple regex — avoids adding a YAML dep)
382
+ const rewardsMatch = yamlContent.match(/features:[\s\S]*?rewards:\s*(true|false)/);
383
+ const meetingsMatch = yamlContent.match(/features:[\s\S]*?meetings:\s*(true|false)/);
384
+ const docsMatch = yamlContent.match(/features:[\s\S]*?docs:\s*(true|false)/);
385
+
386
+ if (rewardsMatch) features.rewards = rewardsMatch[1] === 'true';
387
+ if (meetingsMatch) features.meetings = meetingsMatch[1] === 'true';
388
+ if (docsMatch) features.docs = docsMatch[1] === 'true';
389
+ } catch {
390
+ console.log(' ⚠️ Could not read project.yaml — using default feature flags.');
391
+ }
392
+ }
393
+
394
+ // Build list of schemas to apply
395
+ const schemas = [{ file: 'schema-core.sql', label: 'core (always)' }];
396
+
397
+ if (features.rewards) {
398
+ schemas.push({ file: 'schema-rewards.sql', label: 'rewards' });
399
+ }
400
+ if (features.meetings) {
401
+ schemas.push({ file: 'schema-meetings.sql', label: 'meetings' });
402
+ }
403
+ if (features.docs) {
404
+ schemas.push({ file: 'schema-docs.sql', label: 'docs' });
405
+ }
406
+
407
+ console.log(' 📋 Schemas to apply:');
408
+ for (const s of schemas) {
409
+ console.log(` - ${s.file} (${s.label})`);
410
+ }
411
+ console.log();
412
+
413
+ // Execute each schema
414
+ let applied = 0;
415
+ let failed = 0;
416
+
417
+ for (const s of schemas) {
418
+ const sqlFile = resolve(sqlDir, s.file);
419
+ if (!existsSync(sqlFile)) {
420
+ console.log(` ⚠️ ${s.file} not found — skipped`);
421
+ continue;
422
+ }
423
+
424
+ try {
425
+ execSync(
426
+ `npx wrangler d1 execute ${dbName} --file=sql/${s.file} --remote`,
427
+ { cwd: pmApiDir, stdio: 'pipe' }
428
+ );
429
+ console.log(` ✅ ${s.file} applied`);
430
+ applied++;
431
+ } catch (err) {
432
+ console.log(` ❌ ${s.file} failed: ${err.message}`);
433
+ failed++;
434
+ }
435
+ }
436
+
437
+ console.log();
438
+ if (failed === 0) {
439
+ console.log(` ✅ Migration complete — ${applied} schema(s) applied.`);
440
+ } else {
441
+ console.log(` ⚠️ Migration finished with errors — ${applied} applied, ${failed} failed.`);
442
+ }
443
+ console.log();
444
+ }
445
+
244
446
  // ── Run ─────────────────────────────────────────────────
245
447
 
246
448
  main().catch(err => {
package/lib/doctor.mjs CHANGED
@@ -97,7 +97,44 @@ export async function runDoctor(targetDir, opts = {}) {
97
97
  }, passed, failed, warnings);
98
98
  }
99
99
 
100
- // 7. .gitignore entries
100
+ // 7. Tier 2 checks (pm-api + mcp-pm)
101
+ const pmApiDir = join(targetDir, 'pm-api');
102
+ let hasPmApi = false;
103
+ try { await access(pmApiDir); hasPmApi = true; } catch {}
104
+
105
+ if (hasPmApi) {
106
+ await check('pm-api/package.json exists', async () => {
107
+ await access(join(pmApiDir, 'package.json'));
108
+ }, passed, failed);
109
+
110
+ await check('pm-api/wrangler.toml exists (hydrated)', async () => {
111
+ await access(join(pmApiDir, 'wrangler.toml'));
112
+ try {
113
+ await access(join(pmApiDir, 'wrangler.toml.hbs'));
114
+ throw new Error('wrangler.toml.hbs still exists — hydration incomplete');
115
+ } catch (e) {
116
+ if (e.message.includes('hydration')) throw e;
117
+ }
118
+ }, passed, failed);
119
+
120
+ await check('pm-api/sql/schema-core.sql exists', async () => {
121
+ await access(join(pmApiDir, 'sql', 'schema-core.sql'));
122
+ }, passed, failed);
123
+
124
+ await check('mcp-pm/package.json exists', async () => {
125
+ await access(join(targetDir, 'mcp-pm', 'package.json'));
126
+ }, passed, failed);
127
+
128
+ await check('pm-api/node_modules exists', async () => {
129
+ await access(join(pmApiDir, 'node_modules'));
130
+ }, passed, failed, warnings);
131
+
132
+ await check('mcp-pm/node_modules exists', async () => {
133
+ await access(join(targetDir, 'mcp-pm', 'node_modules'));
134
+ }, passed, failed, warnings);
135
+ }
136
+
137
+ // 8. .gitignore entries
101
138
  await check('.gitignore includes user-context.yaml', async () => {
102
139
  const content = await readFile(join(targetDir, '.gitignore'), 'utf-8');
103
140
  if (!content.includes('user-context.yaml')) {
package/lib/hydrate.mjs CHANGED
@@ -145,6 +145,21 @@ export async function hydrate(targetDir, opts = {}) {
145
145
  if (result) hydrated.push(relative(targetDir, result));
146
146
  }
147
147
 
148
+ // pm-api wrangler.toml.hbs hydration (Tier 2)
149
+ const pmApiWranglerPath = join(targetDir, 'pm-api', 'wrangler.toml.hbs');
150
+ try {
151
+ const wranglerResult = await hydrateFile(pmApiWranglerPath, {
152
+ ctx,
153
+ registry,
154
+ enabledProviders,
155
+ targetType: 'system',
156
+ targetName: 'wrangler.toml',
157
+ });
158
+ if (wranglerResult) hydrated.push(relative(targetDir, wranglerResult));
159
+ } catch {
160
+ // pm-api not present (Tier 0/1) — skip
161
+ }
162
+
148
163
  // Domain command generation
149
164
  const domains = projectYaml.operations?.domains || [];
150
165
  const domainResults = [];
package/lib/scaffold.mjs CHANGED
@@ -57,6 +57,11 @@ async function walk(srcDir, destDir, targetDir, options, copied, overwritten, sk
57
57
  continue;
58
58
  }
59
59
 
60
+ // Skip pm-api and mcp-pm if not Tier 2
61
+ if (options.skipPmApi && (relPath.startsWith('pm-api') || relPath.startsWith('mcp-pm'))) {
62
+ continue;
63
+ }
64
+
60
65
  // Skip manifest.yaml (adapter metadata, not a project file)
61
66
  if (entry.name === 'manifest.yaml') {
62
67
  continue;
@@ -130,6 +130,38 @@ export async function runSetupWizard(targetDir, opts = {}) {
130
130
  specSiteConfig.mode = specSiteTier;
131
131
  }
132
132
 
133
+ // ── Phase 6: Backend Setup (Tier 2) ──────────────
134
+ let pmApiConfig = { enabled: false, url: '', features: { rewards: false, meetings: true, docs: true, initiatives: true }, blockchain: { enabled: false, provider: '', token_name: '', contract_address: '', token_decimals: 8 } };
135
+
136
+ if (specSiteTier === 'interactive') {
137
+ console.log();
138
+ console.log(' ──────────────────────────────────────');
139
+ console.log(' 🖥️ Backend Setup (Tier 2)');
140
+ console.log(' ──────────────────────────────────────');
141
+ console.log();
142
+
143
+ pmApiConfig.enabled = true;
144
+ pmApiConfig.url = await ask(rl, 'PM API URL (leave empty to configure later)');
145
+
146
+ console.log();
147
+ console.log(' Feature modules:');
148
+ pmApiConfig.features.rewards = await confirm(rl, 'Enable rewards/penalties module?', false);
149
+ pmApiConfig.features.meetings = await confirm(rl, 'Enable meetings module?', true);
150
+ pmApiConfig.features.docs = await confirm(rl, 'Enable docs module?', true);
151
+ pmApiConfig.features.initiatives = await confirm(rl, 'Enable initiatives module?', true);
152
+
153
+ console.log();
154
+ const enableBlockchain = await confirm(rl, 'Enable blockchain integration?', false);
155
+ if (enableBlockchain) {
156
+ pmApiConfig.blockchain.enabled = true;
157
+ pmApiConfig.blockchain.provider = await select(rl, 'Blockchain provider:', [
158
+ { label: 'TRON', value: 'tron' },
159
+ ], 0);
160
+ pmApiConfig.blockchain.token_name = await ask(rl, 'Token name');
161
+ pmApiConfig.blockchain.contract_address = await ask(rl, 'Contract address');
162
+ }
163
+ }
164
+
133
165
  console.log();
134
166
 
135
167
  // ── Phase 5: Integrations ────────────────────────
@@ -141,7 +173,7 @@ export async function runSetupWizard(targetDir, opts = {}) {
141
173
  // project.yaml
142
174
  const projectYaml = buildProjectYaml({
143
175
  projectName, tagline, projectType,
144
- domains, devScope, integrations, specSiteConfig,
176
+ domains, devScope, integrations, specSiteConfig, pmApiConfig,
145
177
  platform: opts.platform || null,
146
178
  industryPreset,
147
179
  });
@@ -356,7 +388,7 @@ export const ALL_INTEGRATION_PROVIDERS = [
356
388
  'sqlite_lambda',
357
389
  ];
358
390
 
359
- function buildProjectYaml({ projectName, tagline, projectType, domains, devScope, integrations, specSiteConfig, platform, industryPreset }) {
391
+ function buildProjectYaml({ projectName, tagline, projectType, domains, devScope, integrations, specSiteConfig, pmApiConfig, platform, industryPreset }) {
360
392
  // Build the full integrations block with all known providers
361
393
  const integrationsBlock = {};
362
394
 
@@ -418,6 +450,7 @@ function buildProjectYaml({ projectName, tagline, projectType, domains, devScope
418
450
  deploy_url: '',
419
451
  backend: specSiteConfig.backend,
420
452
  },
453
+ pm_api: pmApiConfig || { enabled: false },
421
454
  },
422
455
  _meta: {
423
456
  created_at: new Date().toISOString(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "popilot",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Multi-agent PO/PM system scaffold for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -103,8 +103,27 @@ operations:
103
103
  service_repo: "" # {{dev_scope.service_repo}}
104
104
 
105
105
  spec_site:
106
+ enabled: false
107
+ mode: "static" # static | interactive
106
108
  title: "" # {{spec_site.title}}
107
109
  deploy_url: "" # {{spec_site.deploy_url}}
110
+ backend: ""
111
+
112
+ # PM API Backend (Tier 2 — Full interactive mode)
113
+ pm_api:
114
+ enabled: false
115
+ url: "" # PM API URL (e.g. https://pm-api.your-domain.workers.dev)
116
+ features:
117
+ rewards: false
118
+ meetings: true
119
+ docs: true
120
+ initiatives: true
121
+ blockchain:
122
+ enabled: false
123
+ provider: "" # tron | (future: eth, solana)
124
+ token_name: ""
125
+ contract_address: ""
126
+ token_decimals: 8
108
127
 
109
128
  # ============================================================
110
129
  # Metadata
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "mcp-pm",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch"
10
+ },
11
+ "dependencies": {
12
+ "@modelcontextprotocol/sdk": "^1.12.1",
13
+ "zod": "^3.23.0"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.7.0",
17
+ "@types/node": "^22.0.0"
18
+ }
19
+ }
@@ -0,0 +1,69 @@
1
+ // PM API HTTP client
2
+ // All MCP tools use this client to communicate with the PM API server.
3
+ // Required env vars: PM_API_URL, PM_TOKEN
4
+
5
+ const PM_API_URL = process.env.PM_API_URL ?? ''
6
+ const PM_TOKEN = process.env.PM_TOKEN ?? ''
7
+
8
+ interface ApiResult<T> {
9
+ data: T | null
10
+ error?: string
11
+ }
12
+
13
+ async function request<T>(method: string, path: string, body?: unknown): Promise<ApiResult<T>> {
14
+ if (!PM_API_URL || !PM_TOKEN) {
15
+ return { data: null, error: 'Missing PM_API_URL or PM_TOKEN' }
16
+ }
17
+
18
+ try {
19
+ const resp = await fetch(`${PM_API_URL}${path}`, {
20
+ method,
21
+ headers: {
22
+ Authorization: `Bearer ${PM_TOKEN}`,
23
+ 'Content-Type': 'application/json',
24
+ },
25
+ body: body ? JSON.stringify(body) : undefined,
26
+ signal: AbortSignal.timeout(15000),
27
+ })
28
+
29
+ const data = await resp.json()
30
+
31
+ if (!resp.ok) {
32
+ return { data: null, error: data.error ?? `HTTP ${resp.status}` }
33
+ }
34
+
35
+ return { data: data as T }
36
+ } catch (err: unknown) {
37
+ const message = err instanceof Error ? err.message : 'Unknown error'
38
+ return { data: null, error: message }
39
+ }
40
+ }
41
+
42
+ export function apiGet<T>(path: string, params?: Record<string, string>): Promise<ApiResult<T>> {
43
+ let url = path
44
+ if (params) {
45
+ const search = new URLSearchParams()
46
+ for (const [k, v] of Object.entries(params)) {
47
+ if (v) search.set(k, v)
48
+ }
49
+ const qs = search.toString()
50
+ if (qs) url += '?' + qs
51
+ }
52
+ return request<T>('GET', url)
53
+ }
54
+
55
+ export function apiPost<T>(path: string, body: unknown): Promise<ApiResult<T>> {
56
+ return request<T>('POST', path, body)
57
+ }
58
+
59
+ export function apiPatch<T>(path: string, body: unknown): Promise<ApiResult<T>> {
60
+ return request<T>('PATCH', path, body)
61
+ }
62
+
63
+ export function apiPut<T>(path: string, body: unknown): Promise<ApiResult<T>> {
64
+ return request<T>('PUT', path, body)
65
+ }
66
+
67
+ export function apiDelete<T>(path: string): Promise<ApiResult<T>> {
68
+ return request<T>('DELETE', path)
69
+ }