youmd 0.8.0 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/commands/chat.d.ts.map +1 -1
  2. package/dist/commands/chat.js +20 -2
  3. package/dist/commands/chat.js.map +1 -1
  4. package/dist/commands/env.d.ts +21 -4
  5. package/dist/commands/env.d.ts.map +1 -1
  6. package/dist/commands/env.js +250 -24
  7. package/dist/commands/env.js.map +1 -1
  8. package/dist/commands/login.d.ts +1 -0
  9. package/dist/commands/login.d.ts.map +1 -1
  10. package/dist/commands/login.js +20 -4
  11. package/dist/commands/login.js.map +1 -1
  12. package/dist/commands/machine.d.ts +28 -1
  13. package/dist/commands/machine.d.ts.map +1 -1
  14. package/dist/commands/machine.js +529 -4
  15. package/dist/commands/machine.js.map +1 -1
  16. package/dist/commands/project.d.ts.map +1 -1
  17. package/dist/commands/project.js +862 -17
  18. package/dist/commands/project.js.map +1 -1
  19. package/dist/commands/prompts.d.ts +19 -0
  20. package/dist/commands/prompts.d.ts.map +1 -1
  21. package/dist/commands/prompts.js +304 -1
  22. package/dist/commands/prompts.js.map +1 -1
  23. package/dist/commands/pull.d.ts.map +1 -1
  24. package/dist/commands/pull.js +28 -8
  25. package/dist/commands/pull.js.map +1 -1
  26. package/dist/commands/push.d.ts +3 -1
  27. package/dist/commands/push.d.ts.map +1 -1
  28. package/dist/commands/push.js +26 -12
  29. package/dist/commands/push.js.map +1 -1
  30. package/dist/commands/skill.d.ts.map +1 -1
  31. package/dist/commands/skill.js +19 -15
  32. package/dist/commands/skill.js.map +1 -1
  33. package/dist/commands/stack.d.ts.map +1 -1
  34. package/dist/commands/stack.js +20 -17
  35. package/dist/commands/stack.js.map +1 -1
  36. package/dist/commands/status.d.ts.map +1 -1
  37. package/dist/commands/status.js +38 -0
  38. package/dist/commands/status.js.map +1 -1
  39. package/dist/commands/sync.d.ts +2 -0
  40. package/dist/commands/sync.d.ts.map +1 -1
  41. package/dist/commands/sync.js +235 -2
  42. package/dist/commands/sync.js.map +1 -1
  43. package/dist/index.js +157 -31
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib/api.d.ts +398 -0
  46. package/dist/lib/api.d.ts.map +1 -1
  47. package/dist/lib/api.js +104 -0
  48. package/dist/lib/api.js.map +1 -1
  49. package/dist/lib/daemon.d.ts +26 -0
  50. package/dist/lib/daemon.d.ts.map +1 -0
  51. package/dist/lib/daemon.js +187 -0
  52. package/dist/lib/daemon.js.map +1 -0
  53. package/dist/lib/first-run.d.ts +1 -1
  54. package/dist/lib/first-run.d.ts.map +1 -1
  55. package/dist/lib/first-run.js +8 -0
  56. package/dist/lib/first-run.js.map +1 -1
  57. package/dist/lib/machine-bootstrap-prompt.d.ts +17 -0
  58. package/dist/lib/machine-bootstrap-prompt.d.ts.map +1 -0
  59. package/dist/lib/machine-bootstrap-prompt.js +426 -0
  60. package/dist/lib/machine-bootstrap-prompt.js.map +1 -0
  61. package/dist/lib/machine-projects.d.ts +71 -0
  62. package/dist/lib/machine-projects.d.ts.map +1 -0
  63. package/dist/lib/machine-projects.js +510 -0
  64. package/dist/lib/machine-projects.js.map +1 -0
  65. package/dist/lib/machine-verify.d.ts +175 -0
  66. package/dist/lib/machine-verify.d.ts.map +1 -0
  67. package/dist/lib/machine-verify.js +684 -0
  68. package/dist/lib/machine-verify.js.map +1 -0
  69. package/dist/lib/portfolio-audit.d.ts +34 -0
  70. package/dist/lib/portfolio-audit.d.ts.map +1 -0
  71. package/dist/lib/portfolio-audit.js +254 -0
  72. package/dist/lib/portfolio-audit.js.map +1 -0
  73. package/dist/lib/portfolio-graph.d.ts +104 -0
  74. package/dist/lib/portfolio-graph.d.ts.map +1 -0
  75. package/dist/lib/portfolio-graph.js +281 -0
  76. package/dist/lib/portfolio-graph.js.map +1 -0
  77. package/dist/lib/project-strategy.d.ts +45 -0
  78. package/dist/lib/project-strategy.d.ts.map +1 -0
  79. package/dist/lib/project-strategy.js +234 -0
  80. package/dist/lib/project-strategy.js.map +1 -0
  81. package/dist/lib/realtime-sync.d.ts +128 -0
  82. package/dist/lib/realtime-sync.d.ts.map +1 -0
  83. package/dist/lib/realtime-sync.js +215 -0
  84. package/dist/lib/realtime-sync.js.map +1 -0
  85. package/dist/lib/reusable-patterns.d.ts +30 -0
  86. package/dist/lib/reusable-patterns.d.ts.map +1 -0
  87. package/dist/lib/reusable-patterns.js +375 -0
  88. package/dist/lib/reusable-patterns.js.map +1 -0
  89. package/dist/lib/skill-catalog.d.ts.map +1 -1
  90. package/dist/lib/skill-catalog.js +20 -0
  91. package/dist/lib/skill-catalog.js.map +1 -1
  92. package/dist/lib/skills.d.ts +1 -0
  93. package/dist/lib/skills.d.ts.map +1 -1
  94. package/dist/lib/skills.js +43 -13
  95. package/dist/lib/skills.js.map +1 -1
  96. package/dist/lib/version.d.ts +2 -0
  97. package/dist/lib/version.d.ts.map +1 -0
  98. package/dist/lib/version.js +63 -0
  99. package/dist/lib/version.js.map +1 -0
  100. package/dist/mcp/registry.d.ts +5 -1
  101. package/dist/mcp/registry.d.ts.map +1 -1
  102. package/dist/mcp/registry.js +426 -2
  103. package/dist/mcp/registry.js.map +1 -1
  104. package/dist/mcp/server.d.ts +2 -0
  105. package/dist/mcp/server.d.ts.map +1 -1
  106. package/dist/mcp/server.js +50 -5
  107. package/dist/mcp/server.js.map +1 -1
  108. package/package.json +2 -1
  109. package/scripts/env-vault/README.md +21 -2
  110. package/scripts/env-vault/backup.sh +24 -1
  111. package/scripts/env-vault/restore.sh +123 -8
  112. package/scripts/skillstack-sync/README.md +35 -10
  113. package/scripts/skillstack-sync/bootstrap-new-mac.sh +31 -0
  114. package/scripts/skillstack-sync/capture-agent-config.sh +1 -0
  115. package/scripts/skillstack-sync/com.youmd.context-sync.plist +38 -0
  116. package/scripts/skillstack-sync/com.youmd.identity-sync.plist +3 -1
  117. package/scripts/skillstack-sync/com.youmd.realtime-sync.plist +35 -0
  118. package/scripts/skillstack-sync/com.youmd.skillstack-sync.plist +1 -1
  119. package/scripts/skillstack-sync/context-sync.sh +69 -1
  120. package/scripts/skillstack-sync/install-daemons.sh +10 -3
  121. package/skills/machine-bootstrap.md +312 -0
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * ─── MIGRATION STATUS ───────────────────────────────────────────────────────
14
14
  *
15
- * MIGRATED (17 tools):
15
+ * MIGRATED (22 tools):
16
16
  * whoami — local bundle read, no auth required
17
17
  * get_identity — local bundle read, compact/full/json/markdown
18
18
  * list_skills — pure local dir scan
@@ -31,6 +31,10 @@
31
31
  * get_remote_status — conditional auth branch + ctx.apiRequest
32
32
  * use_skill — ctx.getInstalledSkills()
33
33
  * get_activity_log — auth-gated fetch via ctx.fetchActivityLog
34
+ * upsert_portfolio_task — authenticated write via ctx.apiRequest
35
+ * update_portfolio_task — authenticated task triage via ctx.apiRequest
36
+ * hydrate_portfolio_graph — authenticated portfolio hydration via ctx.apiRequest
37
+ * record_brain_dump — authenticated write via ctx.apiRequest
34
38
  *
35
39
  * DEFERRED (4 tools — write-ops and dynamic-import tools):
36
40
  * get_agent_brief — >50 lines; delegates to buildAgentBrief() in server.ts
@@ -84,6 +88,7 @@ const config_1 = require("../lib/config");
84
88
  const project_1 = require("../lib/project");
85
89
  const projectContext_1 = require("../lib/projectContext");
86
90
  const youstack_1 = require("../lib/youstack");
91
+ const portfolio_graph_1 = require("../lib/portfolio-graph");
87
92
  // ─── Shared config/helpers (duplicated here to avoid circular server.ts dep) ──
88
93
  function _getLocalBundleDir2() { return (0, config_1.getLocalBundleDir)(); }
89
94
  function _getHomeBundleDir2() { return (0, config_1.getHomeBundleDir)(); }
@@ -221,6 +226,19 @@ function tryLoadStack(inputPath) {
221
226
  return null;
222
227
  }
223
228
  }
229
+ async function getProjectPortfolioSlice(projectName, ctx) {
230
+ if (ctx.authenticated) {
231
+ try {
232
+ const snapshot = await ctx.apiRequest("/api/v1/me/portfolio/graph?includeTasks=1");
233
+ const brief = (0, portfolio_graph_1.portfolioGraphBriefFromSnapshot)(snapshot);
234
+ return (0, portfolio_graph_1.getPortfolioGraphProjectSlice)(projectName, brief);
235
+ }
236
+ catch {
237
+ // Use the bundled fallback when the local agent is offline or unauthenticated.
238
+ }
239
+ }
240
+ return (0, portfolio_graph_1.getPortfolioGraphProjectSlice)(projectName);
241
+ }
224
242
  // ─── Registry ─────────────────────────────────────────────────────────────────
225
243
  exports.CLI_MCP_TOOLS = [
226
244
  // ── whoami ──────────────────────────────────────────────────────────────────
@@ -362,11 +380,13 @@ exports.CLI_MCP_TOOLS = [
362
380
  });
363
381
  if (projectName) {
364
382
  const ctx2 = readMergedProjectContext({ projectName });
383
+ const portfolioGraph = await getProjectPortfolioSlice(projectName, ctx);
365
384
  if (!ctx2) {
366
385
  const result = {
367
386
  readiness: readiness("not_found", `Project context for ${projectName} was not found or is incomplete.`, "Use local repo instructions, project-context files, or confirm the named project exists before retrying."),
368
387
  project: { name: projectName, source: "named" },
369
388
  projectContext: null,
389
+ portfolioGraph,
370
390
  };
371
391
  ctx.logActivity("read", "project/" + projectName);
372
392
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], isError: true };
@@ -375,6 +395,7 @@ exports.CLI_MCP_TOOLS = [
375
395
  readiness: readiness("ready", `Project context for ${projectName} is available.`, "None needed."),
376
396
  project: { name: projectName, source: "named" },
377
397
  projectContext: ctx2,
398
+ portfolioGraph,
378
399
  };
379
400
  ctx.logActivity("read", "project/" + projectName);
380
401
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
@@ -392,16 +413,19 @@ exports.CLI_MCP_TOOLS = [
392
413
  }
393
414
  const finalCtx = merged;
394
415
  const name = finalCtx?.meta?.name || current?.name || null;
416
+ const portfolioGraph = await getProjectPortfolioSlice(name, ctx);
395
417
  const result = finalCtx
396
418
  ? {
397
419
  readiness: readiness("ready", `Project context for ${name} is available.`, "None needed."),
398
420
  project: { name, source: "current" },
399
421
  projectContext: finalCtx,
422
+ portfolioGraph,
400
423
  }
401
424
  : {
402
425
  readiness: readiness("not_found", "Project context could not be read.", "Use repo-local instructions and project-context markdown files directly."),
403
426
  project: { name, source: "current" },
404
427
  projectContext: null,
428
+ portfolioGraph,
405
429
  };
406
430
  ctx.logActivity("read", "project/current");
407
431
  return {
@@ -642,6 +666,406 @@ exports.CLI_MCP_TOOLS = [
642
666
  }
643
667
  },
644
668
  },
669
+ // ── upsert_portfolio_task ───────────────────────────────────────────────────
670
+ {
671
+ name: "upsert_portfolio_task",
672
+ description: "Create a project-associated or personal portfolio task for the user. Use when a local agent needs to assign work to the human or to agents and sync the task into You.md's portfolio graph/repo snapshot. Requires authentication.",
673
+ inputSchema: {
674
+ type: "object",
675
+ properties: {
676
+ title: { type: "string", description: "Task title." },
677
+ project: { type: "string", description: "Optional project slug/name. Omit or use personal for uncategorized personal tasks." },
678
+ description: { type: "string", description: "Optional task details." },
679
+ owner_type: { type: "string", enum: ["human", "agent"], description: "Who owns this task: human or agent." },
680
+ owner_label: { type: "string", description: "Optional owner label, such as Houston, Codex, Claude, or You Agent." },
681
+ status: { type: "string", description: "Task status: proposed, open, in_progress, done, snoozed, cancelled." },
682
+ priority: { type: "string", description: "Priority: low, normal, high, urgent." },
683
+ tags: { type: "array", items: { type: "string" }, description: "Optional searchable tags." },
684
+ sync_repo: { type: "boolean", description: "Whether to publish and sync the GitHub repo snapshot. Defaults true." },
685
+ },
686
+ required: ["title", "owner_type"],
687
+ },
688
+ handler: async (args, ctx) => {
689
+ if (!ctx.authenticated) {
690
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
691
+ }
692
+ const title = typeof args.title === "string" ? args.title.trim() : "";
693
+ if (!title) {
694
+ return { content: [{ type: "text", text: "missing required argument: title" }], isError: true };
695
+ }
696
+ const ownerType = args.owner_type === "human" ? "human" : "agent";
697
+ const body = {
698
+ title,
699
+ projectSlug: typeof args.project === "string" ? args.project : undefined,
700
+ description: typeof args.description === "string" ? args.description : undefined,
701
+ ownerType,
702
+ ownerLabel: typeof args.owner_label === "string" ? args.owner_label : ctx.resolveAgentName(),
703
+ status: typeof args.status === "string" ? args.status : "open",
704
+ priority: typeof args.priority === "string" ? args.priority : "normal",
705
+ tags: Array.isArray(args.tags) ? args.tags.filter((tag) => typeof tag === "string") : [],
706
+ syncRepo: args.sync_repo !== false,
707
+ sourceType: "mcp",
708
+ agentName: ctx.resolveAgentName(),
709
+ };
710
+ try {
711
+ const result = await ctx.apiRequest("/api/v1/me/portfolio/tasks", {
712
+ method: "POST",
713
+ body,
714
+ });
715
+ const ok = result.success === true;
716
+ ctx.logActivity("write", "portfolio/task", { title, project: body.projectSlug ?? "personal" });
717
+ return {
718
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
719
+ isError: !ok,
720
+ };
721
+ }
722
+ catch (err) {
723
+ return {
724
+ content: [{ type: "text", text: `failed to save portfolio task: ${err instanceof Error ? err.message : "unknown error"}` }],
725
+ isError: true,
726
+ };
727
+ }
728
+ },
729
+ },
730
+ // ── update_portfolio_task ───────────────────────────────────────────────────
731
+ {
732
+ name: "update_portfolio_task",
733
+ description: "Update an existing portfolio task's triage, owner, project scope, title, details, due date, or tags. Use after an agent starts, finishes, reassigns, routes, or enriches a task in You.md's persisted project graph. Requires authentication.",
734
+ inputSchema: {
735
+ type: "object",
736
+ properties: {
737
+ task_id: { type: "string", description: "Existing portfolio task id." },
738
+ title: { type: "string", description: "Updated task title." },
739
+ project: { type: ["string", "null"], description: "Updated project slug/name. Use null or personal to move to uncategorized personal tasks." },
740
+ description: { type: ["string", "null"], description: "Updated task details. Use null to clear." },
741
+ owner_type: { type: "string", enum: ["human", "agent"], description: "Who owns this task: human or agent." },
742
+ owner_label: { type: ["string", "null"], description: "Optional owner label, such as Houston, Codex, Claude, or You Agent. Use null to clear." },
743
+ status: { type: "string", enum: ["proposed", "open", "in_progress", "done", "snoozed", "cancelled"], description: "New task status." },
744
+ priority: { type: "string", enum: ["low", "normal", "high", "urgent"], description: "New task priority." },
745
+ due_at: { type: ["number", "null"], description: "Due timestamp in milliseconds. Use null to clear." },
746
+ tags: { type: "array", items: { type: "string" }, description: "Replacement tag list." },
747
+ sync_repo: { type: "boolean", description: "Whether to publish and sync the GitHub repo snapshot. Defaults true." },
748
+ },
749
+ required: ["task_id"],
750
+ },
751
+ handler: async (args, ctx) => {
752
+ if (!ctx.authenticated) {
753
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
754
+ }
755
+ const taskId = typeof args.task_id === "string" ? args.task_id.trim() : "";
756
+ if (!taskId) {
757
+ return { content: [{ type: "text", text: "missing required argument: task_id" }], isError: true };
758
+ }
759
+ const status = typeof args.status === "string" ? args.status.trim() : undefined;
760
+ const priority = typeof args.priority === "string" ? args.priority.trim() : undefined;
761
+ const body = {
762
+ taskId,
763
+ syncRepo: args.sync_repo !== false,
764
+ agentName: ctx.resolveAgentName(),
765
+ };
766
+ if (typeof args.title === "string")
767
+ body.title = args.title.trim();
768
+ if ("project" in args)
769
+ body.projectSlug = args.project === null ? null : args.project;
770
+ if ("description" in args)
771
+ body.description = args.description === null ? null : args.description;
772
+ if (args.owner_type === "human" || args.owner_type === "agent")
773
+ body.ownerType = args.owner_type;
774
+ if ("owner_label" in args)
775
+ body.ownerLabel = args.owner_label === null ? null : args.owner_label;
776
+ if (status)
777
+ body.status = status;
778
+ if (priority)
779
+ body.priority = priority;
780
+ if ("due_at" in args)
781
+ body.dueAt = args.due_at === null ? null : args.due_at;
782
+ if (Array.isArray(args.tags))
783
+ body.tags = args.tags.filter((tag) => typeof tag === "string");
784
+ if (Object.keys(body).length <= 3) {
785
+ return { content: [{ type: "text", text: "at least one task field is required" }], isError: true };
786
+ }
787
+ try {
788
+ const result = await ctx.apiRequest("/api/v1/me/portfolio/tasks/update", {
789
+ method: "POST",
790
+ body,
791
+ });
792
+ const ok = result.success === true;
793
+ ctx.logActivity("write", "portfolio/task-update", { taskId, fields: Object.keys(body).filter((field) => !["taskId", "syncRepo", "agentName"].includes(field)) });
794
+ return {
795
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
796
+ isError: !ok,
797
+ };
798
+ }
799
+ catch (err) {
800
+ return {
801
+ content: [{ type: "text", text: `failed to update portfolio task: ${err instanceof Error ? err.message : "unknown error"}` }],
802
+ isError: true,
803
+ };
804
+ }
805
+ },
806
+ },
807
+ // ── hydrate_portfolio_graph ────────────────────────────────────────────────
808
+ {
809
+ name: "hydrate_portfolio_graph",
810
+ description: "Hydrate the persisted You.md Portfolio Graph from the authenticated recent GitHub project catalog. Use after refreshing/analyzing active projects so the Portfolio Graph is not stuck on bootstrap seed rows.",
811
+ inputSchema: {
812
+ type: "object",
813
+ properties: {
814
+ days: { type: "number", description: "Recent activity window in days. Defaults to 90." },
815
+ limit: { type: "number", description: "Maximum tracked projects to hydrate. Defaults to 80." },
816
+ include_github: { type: "boolean", description: "Whether to hydrate from tracked GitHub projects. Defaults true." },
817
+ },
818
+ },
819
+ handler: async (args, ctx) => {
820
+ if (!ctx.authenticated) {
821
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
822
+ }
823
+ const days = typeof args.days === "number" && Number.isFinite(args.days) ? args.days : 90;
824
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? args.limit : 80;
825
+ try {
826
+ const result = await ctx.apiRequest("/api/v1/me/portfolio/projects/hydrate", {
827
+ method: "POST",
828
+ body: {
829
+ includeTracked: args.include_github !== false,
830
+ days,
831
+ limit,
832
+ projects: [],
833
+ source: "mcp-portfolio-hydrate",
834
+ agentName: ctx.resolveAgentName(),
835
+ },
836
+ });
837
+ const ok = result.success === true;
838
+ ctx.logActivity("write", "portfolio/projects", { days, limit });
839
+ return {
840
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
841
+ isError: !ok,
842
+ };
843
+ }
844
+ catch (err) {
845
+ return {
846
+ content: [{ type: "text", text: `failed to hydrate portfolio graph: ${err instanceof Error ? err.message : "unknown error"}` }],
847
+ isError: true,
848
+ };
849
+ }
850
+ },
851
+ },
852
+ // ── upsert_portfolio_project ───────────────────────────────────────────────
853
+ {
854
+ name: "upsert_portfolio_project",
855
+ description: "Create or update a project strategy/intelligence record in You.md's persisted Portfolio Graph. Use before adding APIs/MCPs/stacks/tasks so agents share the same goal, vision, pain points, constraints, non-goals, metrics, repo, and docs context.",
856
+ inputSchema: {
857
+ type: "object",
858
+ properties: {
859
+ name: { type: "string", description: "Project display name." },
860
+ slug: { type: "string", description: "Stable project slug." },
861
+ stack_name: { type: "string", description: "Associated stack name, such as YouStack or BAMFStack." },
862
+ status: { type: "string", description: "Project status, such as active, build, research, template, or audit." },
863
+ summary: { type: "string", description: "Short AI/human summary." },
864
+ detailed_description: { type: "string", description: "Longer project description." },
865
+ goal: { type: "string", description: "High-level goal." },
866
+ vision: { type: "string", description: "North-star vision." },
867
+ focus: { type: "string", description: "Current focus." },
868
+ positioning: { type: "string", description: "Positioning/category." },
869
+ audience: { type: "string", description: "Who this is for." },
870
+ pain_points: { type: "array", items: { type: "string" }, description: "Pain points solved." },
871
+ solution: { type: "string", description: "Solution summary." },
872
+ why_this_solution: { type: "string", description: "Why this solution is right." },
873
+ north_star: { type: "string", description: "North-star metric or guiding outcome." },
874
+ metrics: { type: "array", items: { type: "string" }, description: "Metrics to track." },
875
+ constraints: { type: "array", items: { type: "string" }, description: "Constraints/guardrails." },
876
+ not_building: { type: "array", items: { type: "string" }, description: "Explicit non-goals." },
877
+ competitors: {
878
+ type: "array",
879
+ description: "Alternatives or competitors to track.",
880
+ items: {
881
+ type: "object",
882
+ properties: {
883
+ name: { type: "string" },
884
+ url: { type: "string" },
885
+ note: { type: "string" },
886
+ },
887
+ required: ["name"],
888
+ },
889
+ },
890
+ repo_full_name: { type: "string", description: "GitHub repo full name, owner/repo." },
891
+ repo_url: { type: "string", description: "Repository URL." },
892
+ product_url: { type: "string", description: "Product/site URL." },
893
+ docs: { type: "array", items: { type: "string" }, description: "Important docs paths or URLs." },
894
+ environments: { type: "array", items: { type: "string" }, description: "Known environments." },
895
+ tags: { type: "array", items: { type: "string" }, description: "Search/routing tags." },
896
+ repo_path: { type: "string", description: "Local repo path or directory name." },
897
+ },
898
+ required: ["name"],
899
+ },
900
+ handler: async (args, ctx) => {
901
+ if (!ctx.authenticated) {
902
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
903
+ }
904
+ const name = typeof args.name === "string" ? args.name.trim() : "";
905
+ if (!name) {
906
+ return { content: [{ type: "text", text: "missing required argument: name" }], isError: true };
907
+ }
908
+ const stringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
909
+ const competitors = Array.isArray(args.competitors)
910
+ ? args.competitors.flatMap((item) => {
911
+ if (!item || typeof item !== "object")
912
+ return [];
913
+ const row = item;
914
+ const competitorName = typeof row.name === "string" ? row.name.trim() : "";
915
+ if (!competitorName)
916
+ return [];
917
+ return [{
918
+ name: competitorName,
919
+ url: typeof row.url === "string" ? row.url : undefined,
920
+ note: typeof row.note === "string" ? row.note : undefined,
921
+ }];
922
+ })
923
+ : [];
924
+ const project = {
925
+ name,
926
+ slug: typeof args.slug === "string" ? args.slug : undefined,
927
+ stackName: typeof args.stack_name === "string" ? args.stack_name : undefined,
928
+ status: typeof args.status === "string" ? args.status : "active",
929
+ summary: typeof args.summary === "string" ? args.summary : undefined,
930
+ detailedDescription: typeof args.detailed_description === "string" ? args.detailed_description : undefined,
931
+ goal: typeof args.goal === "string" ? args.goal : undefined,
932
+ vision: typeof args.vision === "string" ? args.vision : undefined,
933
+ focus: typeof args.focus === "string" ? args.focus : undefined,
934
+ positioning: typeof args.positioning === "string" ? args.positioning : undefined,
935
+ audience: typeof args.audience === "string" ? args.audience : undefined,
936
+ painPoints: stringArray(args.pain_points),
937
+ solution: typeof args.solution === "string" ? args.solution : undefined,
938
+ whyThisSolution: typeof args.why_this_solution === "string" ? args.why_this_solution : undefined,
939
+ northStar: typeof args.north_star === "string" ? args.north_star : undefined,
940
+ metrics: stringArray(args.metrics),
941
+ constraints: stringArray(args.constraints),
942
+ notBuilding: stringArray(args.not_building),
943
+ competitors,
944
+ repoFullName: typeof args.repo_full_name === "string" ? args.repo_full_name : undefined,
945
+ repoUrl: typeof args.repo_url === "string" ? args.repo_url : undefined,
946
+ productUrl: typeof args.product_url === "string" ? args.product_url : undefined,
947
+ docs: stringArray(args.docs),
948
+ environments: stringArray(args.environments),
949
+ tags: stringArray(args.tags),
950
+ path: typeof args.repo_path === "string" ? args.repo_path : undefined,
951
+ };
952
+ try {
953
+ const result = await ctx.apiRequest("/api/v1/me/portfolio/projects/hydrate", {
954
+ method: "POST",
955
+ body: {
956
+ includeTracked: false,
957
+ projects: [project],
958
+ source: "mcp-project-enrichment",
959
+ agentName: ctx.resolveAgentName(),
960
+ },
961
+ });
962
+ const ok = result.success === true;
963
+ ctx.logActivity("write", "portfolio/project", { name, slug: project.slug });
964
+ return {
965
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
966
+ isError: !ok,
967
+ };
968
+ }
969
+ catch (err) {
970
+ return {
971
+ content: [{ type: "text", text: `failed to upsert portfolio project: ${err instanceof Error ? err.message : "unknown error"}` }],
972
+ isError: true,
973
+ };
974
+ }
975
+ },
976
+ },
977
+ // ── record_brain_dump ───────────────────────────────────────────────────────
978
+ {
979
+ name: "record_brain_dump",
980
+ description: "Save a raw brain dump, route it to projects/tags, and optionally create proposed human/agent tasks. Use for pasted notes, terminal thoughts, SMS/watch transcripts, and local-agent research that should not die in one-off files. Requires authentication.",
981
+ inputSchema: {
982
+ type: "object",
983
+ properties: {
984
+ raw_text: { type: "string", description: "Raw unedited brain dump or transcript text." },
985
+ summary: { type: "string", description: "Short summary. Omit to let the API derive one." },
986
+ projects: { type: "array", items: { type: "string" }, description: "Related project slugs/names." },
987
+ tags: { type: "array", items: { type: "string" }, description: "Topic tags." },
988
+ insights: { type: "array", items: { type: "string" }, description: "Interesting insights or through-lines." },
989
+ source: { type: "string", description: "Capture source: mcp, cli, sms, watch, badapp, shell, manual." },
990
+ tasks: {
991
+ type: "array",
992
+ description: "Optional proposed tasks extracted from the dump.",
993
+ items: {
994
+ type: "object",
995
+ properties: {
996
+ title: { type: "string" },
997
+ projectSlug: { type: "string" },
998
+ description: { type: "string" },
999
+ ownerType: { type: "string", enum: ["human", "agent"] },
1000
+ ownerLabel: { type: "string" },
1001
+ priority: { type: "string" },
1002
+ tags: { type: "array", items: { type: "string" } },
1003
+ },
1004
+ required: ["title", "ownerType"],
1005
+ },
1006
+ },
1007
+ sync_repo: { type: "boolean", description: "Whether to publish and sync the GitHub repo snapshot. Defaults true." },
1008
+ },
1009
+ required: ["raw_text"],
1010
+ },
1011
+ handler: async (args, ctx) => {
1012
+ if (!ctx.authenticated) {
1013
+ return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
1014
+ }
1015
+ const rawText = typeof args.raw_text === "string" ? args.raw_text.trim() : "";
1016
+ if (!rawText) {
1017
+ return { content: [{ type: "text", text: "missing required argument: raw_text" }], isError: true };
1018
+ }
1019
+ const arrayOfStrings = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
1020
+ const tasks = Array.isArray(args.tasks)
1021
+ ? args.tasks
1022
+ .map((task) => typeof task === "object" && task !== null ? task : null)
1023
+ .filter((task) => task !== null && typeof task.title === "string")
1024
+ .map((task) => ({
1025
+ title: String(task.title),
1026
+ projectSlug: typeof task.projectSlug === "string" ? task.projectSlug : undefined,
1027
+ description: typeof task.description === "string" ? task.description : undefined,
1028
+ ownerType: task.ownerType === "human" ? "human" : "agent",
1029
+ ownerLabel: typeof task.ownerLabel === "string" ? task.ownerLabel : ctx.resolveAgentName(),
1030
+ priority: typeof task.priority === "string" ? task.priority : "normal",
1031
+ tags: arrayOfStrings(task.tags),
1032
+ }))
1033
+ : [];
1034
+ const body = {
1035
+ rawText,
1036
+ summary: typeof args.summary === "string" ? args.summary : undefined,
1037
+ projectSlugs: arrayOfStrings(args.projects),
1038
+ tags: arrayOfStrings(args.tags),
1039
+ insights: arrayOfStrings(args.insights),
1040
+ source: typeof args.source === "string" ? args.source : "mcp",
1041
+ tasks,
1042
+ syncRepo: args.sync_repo !== false,
1043
+ agentName: ctx.resolveAgentName(),
1044
+ metadata: {
1045
+ command: "record_brain_dump",
1046
+ savedFrom: "cli-mcp",
1047
+ },
1048
+ };
1049
+ try {
1050
+ const result = await ctx.apiRequest("/api/v1/me/portfolio/brain-dumps", {
1051
+ method: "POST",
1052
+ body,
1053
+ });
1054
+ const ok = result.success === true;
1055
+ ctx.logActivity("write", "portfolio/brain-dump", { projects: body.projectSlugs, taskCount: tasks.length });
1056
+ return {
1057
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1058
+ isError: !ok,
1059
+ };
1060
+ }
1061
+ catch (err) {
1062
+ return {
1063
+ content: [{ type: "text", text: `failed to save brain dump: ${err instanceof Error ? err.message : "unknown error"}` }],
1064
+ isError: true,
1065
+ };
1066
+ }
1067
+ },
1068
+ },
645
1069
  // ── add_source ───────────────────────────────────────────────────────────────
646
1070
  {
647
1071
  name: "add_source",
@@ -760,7 +1184,7 @@ exports.CLI_MCP_TOOLS = [
760
1184
  properties: {
761
1185
  name: {
762
1186
  type: "string",
763
- description: "Skill name: youstack-start, youstack-maintainer, voice-sync, claude-md-generator, project-context-init, meta-improve, proactive-context-fill, you-logs",
1187
+ description: "Skill name: youstack-start, youstack-maintainer, machine-bootstrap, portfolio-graph-auditor, voice-sync, claude-md-generator, project-context-init, meta-improve, proactive-context-fill, you-logs",
764
1188
  },
765
1189
  },
766
1190
  required: ["name"],