@useorgx/wizard 0.1.17 → 0.1.20

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.
package/dist/cli.js CHANGED
@@ -1300,6 +1300,39 @@ function parseSkillFiles(value) {
1300
1300
  updatedAt: record.updatedAt.trim()
1301
1301
  };
1302
1302
  }
1303
+ function parsePeopleFirstCaptureEntry(value) {
1304
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1305
+ return void 0;
1306
+ }
1307
+ const record = value;
1308
+ if (!isNonEmptyString2(record.workspaceId) || !isNonEmptyString2(record.completedAt)) {
1309
+ return void 0;
1310
+ }
1311
+ return {
1312
+ workspaceId: record.workspaceId.trim(),
1313
+ completedAt: record.completedAt.trim(),
1314
+ ...isNonEmptyString2(record.personId) ? { personId: record.personId.trim() } : {},
1315
+ ...isNonEmptyString2(record.templatePersona) ? { templatePersona: record.templatePersona.trim() } : {},
1316
+ ...record.stagedLocally === true ? { stagedLocally: true } : {}
1317
+ };
1318
+ }
1319
+ function parsePeopleFirstCapture(value) {
1320
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1321
+ return void 0;
1322
+ }
1323
+ const record = value;
1324
+ if (!isNonEmptyString2(record.updatedAt) || !Array.isArray(record.entries)) {
1325
+ return void 0;
1326
+ }
1327
+ const entries = record.entries.map((entry) => parsePeopleFirstCaptureEntry(entry)).filter(
1328
+ (entry) => Boolean(entry)
1329
+ );
1330
+ if (entries.length === 0) return void 0;
1331
+ return {
1332
+ updatedAt: record.updatedAt.trim(),
1333
+ entries
1334
+ };
1335
+ }
1303
1336
  function createWizardState(now = (/* @__PURE__ */ new Date()).toISOString()) {
1304
1337
  return {
1305
1338
  installationId: `wizard-${randomUUID()}`,
@@ -1314,6 +1347,7 @@ function sanitizeWizardStateRecord(record) {
1314
1347
  const firstValueInitiative = parseFirstValueInitiative(record.firstValueInitiative);
1315
1348
  const onboardingTask = parseOnboardingTask(record.onboardingTask);
1316
1349
  const skillFiles = parseSkillFiles(record.skillFiles);
1350
+ const peopleFirstCapture = parsePeopleFirstCapture(record.peopleFirstCapture);
1317
1351
  return {
1318
1352
  installationId: record.installationId.trim(),
1319
1353
  createdAt: record.createdAt.trim(),
@@ -1323,7 +1357,8 @@ function sanitizeWizardStateRecord(record) {
1323
1357
  ...demoInitiative ? { demoInitiative } : {},
1324
1358
  ...firstValueInitiative ? { firstValueInitiative } : {},
1325
1359
  ...onboardingTask ? { onboardingTask } : {},
1326
- ...skillFiles ? { skillFiles } : {}
1360
+ ...skillFiles ? { skillFiles } : {},
1361
+ ...peopleFirstCapture ? { peopleFirstCapture } : {}
1327
1362
  };
1328
1363
  }
1329
1364
  function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
@@ -1350,8 +1385,32 @@ function readWizardState(statePath = ORGX_WIZARD_STATE_PATH) {
1350
1385
  if (onboardingTask !== void 0) state.onboardingTask = onboardingTask;
1351
1386
  const skillFiles = parseSkillFiles(parsed.skillFiles);
1352
1387
  if (skillFiles !== void 0) state.skillFiles = skillFiles;
1388
+ const peopleFirstCapture = parsePeopleFirstCapture(parsed.peopleFirstCapture);
1389
+ if (peopleFirstCapture !== void 0) state.peopleFirstCapture = peopleFirstCapture;
1353
1390
  return state;
1354
1391
  }
1392
+ function hasPeopleFirstCaptureCompleted(workspaceId, statePath = ORGX_WIZARD_STATE_PATH) {
1393
+ const state = readWizardState(statePath);
1394
+ if (!state?.peopleFirstCapture) return false;
1395
+ return state.peopleFirstCapture.entries.some(
1396
+ (entry) => entry.workspaceId === workspaceId
1397
+ );
1398
+ }
1399
+ function recordPeopleFirstCaptureCompletion(entry, statePath = ORGX_WIZARD_STATE_PATH) {
1400
+ return updateWizardState((current) => {
1401
+ const existingEntries = current.peopleFirstCapture?.entries ?? [];
1402
+ const withoutExisting = existingEntries.filter(
1403
+ (e) => e.workspaceId !== entry.workspaceId
1404
+ );
1405
+ return {
1406
+ ...current,
1407
+ peopleFirstCapture: {
1408
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1409
+ entries: [...withoutExisting, entry]
1410
+ }
1411
+ };
1412
+ }, statePath);
1413
+ }
1355
1414
  function writeWizardState(value, statePath = ORGX_WIZARD_STATE_PATH) {
1356
1415
  const record = sanitizeWizardStateRecord(value);
1357
1416
  writeJsonFile(statePath, record, { mode: 384 });
@@ -4916,6 +4975,7 @@ function countPluginReportChanges(report) {
4916
4975
  // src/lib/setup-workspace.ts
4917
4976
  var CREATE_WORKSPACE_VALUE = "__create_workspace__";
4918
4977
  var SKIP_WORKSPACE_VALUE = "__skip_workspace__";
4978
+ var REAUTH_WORKSPACE_VALUE = "__reauth_workspace__";
4919
4979
  function trimDescription(value) {
4920
4980
  const trimmed = value.trim();
4921
4981
  return trimmed.length > 0 ? trimmed : void 0;
@@ -5025,24 +5085,35 @@ async function runWorkspaceSetup(client, prompts, options) {
5025
5085
  const currentWorkspace = workspaces.length > 0 ? await client.getCurrentWorkspace().catch(() => workspaces.find((workspace) => workspace.isDefault) ?? null) : null;
5026
5086
  if (workspaces.length === 0) {
5027
5087
  const action = await prompts.select({
5028
- message: "No OrgX workspaces were found. What should setup do next?",
5088
+ message: "OrgX authenticated, but returned 0 workspaces for this key. How should setup proceed?",
5029
5089
  options: [
5090
+ {
5091
+ value: REAUTH_WORKSPACE_VALUE,
5092
+ label: "Re-authenticate (recommended if you expected workspaces)",
5093
+ hint: "Run `wizard auth login` to re-link via OAuth if your API key is bound to a different user identity."
5094
+ },
5030
5095
  {
5031
5096
  value: CREATE_WORKSPACE_VALUE,
5032
5097
  label: "Create a new workspace",
5033
- hint: "Create one now and set it as the default for this machine."
5098
+ hint: "Use this only for a truly new account \u2014 this will fork data if your key is misbound."
5034
5099
  },
5035
5100
  {
5036
5101
  value: SKIP_WORKSPACE_VALUE,
5037
5102
  label: "Skip for now",
5038
- hint: "Finish surface setup without creating a workspace."
5103
+ hint: "Finish surface setup without touching workspaces."
5039
5104
  }
5040
5105
  ],
5041
- initialValue: CREATE_WORKSPACE_VALUE
5106
+ initialValue: REAUTH_WORKSPACE_VALUE
5042
5107
  });
5043
5108
  if (prompts.isCancel(action)) {
5044
5109
  return cancelResult(prompts);
5045
5110
  }
5111
+ if (action === REAUTH_WORKSPACE_VALUE) {
5112
+ return {
5113
+ message: "Re-authentication requested. Run `wizard auth login`, then rerun `wizard setup`.",
5114
+ status: "reauth_requested"
5115
+ };
5116
+ }
5046
5117
  if (action === SKIP_WORKSPACE_VALUE) {
5047
5118
  return {
5048
5119
  message: "Workspace bootstrap skipped.",
@@ -5525,6 +5596,536 @@ async function fetchOnboardingState(auth) {
5525
5596
  }
5526
5597
  }
5527
5598
 
5599
+ // src/lib/people-first-capture.ts
5600
+ var RELATIONSHIP_STAGE_OPTIONS = [
5601
+ { value: "stranger", label: "Stranger", hint: "no prior contact yet" },
5602
+ { value: "prospect", label: "Prospect", hint: "outreach planned or in flight (default)" },
5603
+ { value: "conversation", label: "Conversation", hint: "active 2-way dialogue" },
5604
+ { value: "design_partner", label: "Design partner", hint: "paid / equity track underway" },
5605
+ { value: "paused", label: "Paused", hint: "intentional pause, not wrong fit" },
5606
+ { value: "churned", label: "Churned", hint: "ended; history preserved" },
5607
+ { value: "alumni", label: "Alumni", hint: "closed engagement, may return" }
5608
+ ];
5609
+ var TRUST_TIER_OPTIONS = [
5610
+ { value: "cold", label: "Cold / unknown", hint: "new or barely-met" },
5611
+ { value: "warm", label: "Warm", hint: "know each other, some trust built" },
5612
+ { value: "close", label: "Close", hint: "deep trust \u2014 inner-circle candidate" }
5613
+ ];
5614
+ async function fetchPeopleFirstOnboardingState(auth) {
5615
+ try {
5616
+ const res = await fetch(
5617
+ buildOrgxApiUrl("/v1/people/onboarding", auth.baseUrl),
5618
+ {
5619
+ method: "GET",
5620
+ headers: { Authorization: `Bearer ${auth.apiKey}` },
5621
+ signal: AbortSignal.timeout(5e3)
5622
+ }
5623
+ );
5624
+ if (!res.ok) return null;
5625
+ const body = await res.json().catch(() => null);
5626
+ if (!body || !Array.isArray(body.workspaces)) return null;
5627
+ return body;
5628
+ } catch {
5629
+ return null;
5630
+ }
5631
+ }
5632
+ function parseContactChannels(raw) {
5633
+ const channels = [];
5634
+ const parts = raw.split(/[,\n]+/).map((part) => part.trim()).filter(Boolean);
5635
+ for (const part of parts) {
5636
+ if (/^https?:\/\/(?:www\.)?linkedin\.com\//i.test(part) || part.toLowerCase().startsWith("linkedin.com/")) {
5637
+ channels.push({ kind: "linkedin", value: part });
5638
+ } else if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(part)) {
5639
+ channels.push({ kind: "email", value: part });
5640
+ } else if (/^\+?[\d][\d\s()\-.]{5,}$/.test(part)) {
5641
+ channels.push({ kind: "phone", value: part.replace(/\s+/g, "") });
5642
+ } else {
5643
+ channels.push({ kind: "linkedin", value: part });
5644
+ }
5645
+ }
5646
+ return channels;
5647
+ }
5648
+ async function postJson(auth, path, body) {
5649
+ try {
5650
+ const res = await fetch(buildOrgxApiUrl(path, auth.baseUrl), {
5651
+ method: "POST",
5652
+ headers: {
5653
+ Authorization: `Bearer ${auth.apiKey}`,
5654
+ "Content-Type": "application/json"
5655
+ },
5656
+ body: JSON.stringify(body),
5657
+ signal: AbortSignal.timeout(1e4)
5658
+ });
5659
+ const text2 = await res.text().catch(() => "");
5660
+ let parsed = null;
5661
+ if (text2) {
5662
+ try {
5663
+ parsed = JSON.parse(text2);
5664
+ } catch {
5665
+ parsed = text2;
5666
+ }
5667
+ }
5668
+ if (!res.ok) {
5669
+ const detail = parsed && typeof parsed === "object" && parsed && "error" in parsed ? String(parsed.error) : `HTTP ${res.status}`;
5670
+ return { ok: false, error: detail };
5671
+ }
5672
+ return { ok: true, status: res.status, body: parsed };
5673
+ } catch (err) {
5674
+ const message = err instanceof Error ? err.message : String(err);
5675
+ return { ok: false, error: message };
5676
+ }
5677
+ }
5678
+ function extractId(body) {
5679
+ if (body && typeof body === "object") {
5680
+ const candidate = body.id ?? body.data;
5681
+ if (typeof candidate === "string") return candidate;
5682
+ if (candidate && typeof candidate === "object") {
5683
+ const innerId = candidate.id;
5684
+ if (typeof innerId === "string") return innerId;
5685
+ }
5686
+ }
5687
+ return null;
5688
+ }
5689
+ var BACKEND_UNREACHABLE_HINT = "Could not sync with OrgX backend \u2014 check your network / run `orgx-wizard status`.";
5690
+ async function runPeopleFirstCapture(options) {
5691
+ if (!options.interactive) {
5692
+ return {
5693
+ status: "skipped_non_interactive",
5694
+ message: "People-first capture skipped \u2014 not attached to a TTY."
5695
+ };
5696
+ }
5697
+ if (!options.workspace) {
5698
+ return {
5699
+ status: "skipped_no_workspace",
5700
+ message: "People-first capture skipped \u2014 no workspace resolved."
5701
+ };
5702
+ }
5703
+ if (options.alreadyCompleted) {
5704
+ return {
5705
+ status: "skipped_already_completed",
5706
+ message: "People-first capture already completed for this workspace."
5707
+ };
5708
+ }
5709
+ const auth = await resolveOrgxAuth();
5710
+ if (!auth) {
5711
+ return {
5712
+ status: "failed",
5713
+ message: "People-first capture needs OrgX auth.",
5714
+ error: "no_auth"
5715
+ };
5716
+ }
5717
+ const state = await fetchPeopleFirstOnboardingState(auth);
5718
+ if (state) {
5719
+ const row = state.workspaces.find((w) => w.id === options.workspace.id);
5720
+ if (row?.peopleFirstCaptureCompletedAt) {
5721
+ return {
5722
+ status: "skipped_already_completed",
5723
+ message: "People-first capture already completed for this workspace."
5724
+ };
5725
+ }
5726
+ }
5727
+ const { prompts } = options;
5728
+ const proceed = await prompts.confirm({
5729
+ message: "Name the first person OrgX should know about? (takes ~30 seconds; OrgX will pre-draft a first-touch artifact for them)",
5730
+ initialValue: true
5731
+ });
5732
+ if (prompts.isCancel(proceed)) {
5733
+ return { status: "cancelled", message: "People-first capture cancelled." };
5734
+ }
5735
+ if (!proceed) {
5736
+ return {
5737
+ status: "skipped_declined",
5738
+ message: "Skipped \u2014 run the wizard again any time to add your first person."
5739
+ };
5740
+ }
5741
+ const headlineAnswer = await prompts.text({
5742
+ message: 'Who is this person to you? (one line \u2014 e.g. "prospect at Acme", "design partner", "old teammate")',
5743
+ placeholder: "design partner prospect for OrgX",
5744
+ validate(value) {
5745
+ if (!value || !value.trim()) return "Enter one line.";
5746
+ if (value.trim().length > 160) return "Keep it under 160 chars.";
5747
+ return void 0;
5748
+ }
5749
+ });
5750
+ if (prompts.isCancel(headlineAnswer)) {
5751
+ return { status: "cancelled", message: "People-first capture cancelled." };
5752
+ }
5753
+ const headline = typeof headlineAnswer === "string" ? headlineAnswer.trim() : "";
5754
+ const contextAnswer = await prompts.select({
5755
+ initialValue: "personal",
5756
+ message: "Is this on behalf of a business, or a personal relationship?",
5757
+ options: [
5758
+ { value: "personal", label: "Personal", hint: "no business attached yet" },
5759
+ { value: "business", label: "On behalf of a business", hint: "will also create a Business entity" }
5760
+ ]
5761
+ });
5762
+ if (prompts.isCancel(contextAnswer)) {
5763
+ return { status: "cancelled", message: "People-first capture cancelled." };
5764
+ }
5765
+ const hasBusiness = contextAnswer === "business";
5766
+ let businessName;
5767
+ if (hasBusiness) {
5768
+ const businessAnswer = await prompts.text({
5769
+ message: "Business name?",
5770
+ placeholder: "Acme Treasury",
5771
+ validate(value) {
5772
+ if (!value || !value.trim()) return "Enter the business name.";
5773
+ return void 0;
5774
+ }
5775
+ });
5776
+ if (prompts.isCancel(businessAnswer)) {
5777
+ return { status: "cancelled", message: "People-first capture cancelled." };
5778
+ }
5779
+ businessName = typeof businessAnswer === "string" ? businessAnswer.trim() : void 0;
5780
+ }
5781
+ const nameAnswer = await prompts.text({
5782
+ message: "Their name?",
5783
+ placeholder: "Laura Chen",
5784
+ validate(value) {
5785
+ if (!value || !value.trim()) return "Enter a display name.";
5786
+ return void 0;
5787
+ }
5788
+ });
5789
+ if (prompts.isCancel(nameAnswer)) {
5790
+ return { status: "cancelled", message: "People-first capture cancelled." };
5791
+ }
5792
+ const displayName = typeof nameAnswer === "string" ? nameAnswer.trim() : "";
5793
+ const contactAnswer = await prompts.text({
5794
+ message: "How do you reach them? (email, LinkedIn URL, or phone \u2014 comma-separated, at least one)",
5795
+ placeholder: "laura@acme.com, linkedin.com/in/laurachen",
5796
+ validate(value) {
5797
+ if (!value || !value.trim()) return "Enter at least one contact.";
5798
+ const parsed = parseContactChannels(value);
5799
+ if (parsed.length === 0) return "Could not parse any contact channels.";
5800
+ return void 0;
5801
+ }
5802
+ });
5803
+ if (prompts.isCancel(contactAnswer)) {
5804
+ return { status: "cancelled", message: "People-first capture cancelled." };
5805
+ }
5806
+ const contactChannels = parseContactChannels(
5807
+ typeof contactAnswer === "string" ? contactAnswer : ""
5808
+ );
5809
+ const stageAnswer = await prompts.select({
5810
+ initialValue: "prospect",
5811
+ message: "What stage is the relationship?",
5812
+ options: RELATIONSHIP_STAGE_OPTIONS.map((opt) => ({
5813
+ value: opt.value,
5814
+ label: opt.label,
5815
+ ...opt.hint ? { hint: opt.hint } : {}
5816
+ }))
5817
+ });
5818
+ if (prompts.isCancel(stageAnswer)) {
5819
+ return { status: "cancelled", message: "People-first capture cancelled." };
5820
+ }
5821
+ const relationshipStage = stageAnswer;
5822
+ const trustAnswer = await prompts.select({
5823
+ initialValue: "cold",
5824
+ message: "How would you describe the trust level right now?",
5825
+ options: TRUST_TIER_OPTIONS.map((opt) => ({
5826
+ value: opt.value,
5827
+ label: opt.label,
5828
+ ...opt.hint ? { hint: opt.hint } : {}
5829
+ }))
5830
+ });
5831
+ if (prompts.isCancel(trustAnswer)) {
5832
+ return { status: "cancelled", message: "People-first capture cancelled." };
5833
+ }
5834
+ const trustTier = trustAnswer;
5835
+ const innerCircleAnswer = await prompts.confirm({
5836
+ message: "Mark this person as inner-circle? (affects tone of drafted artifacts)",
5837
+ initialValue: false
5838
+ });
5839
+ if (prompts.isCancel(innerCircleAnswer)) {
5840
+ return { status: "cancelled", message: "People-first capture cancelled." };
5841
+ }
5842
+ const innerCircle = Boolean(innerCircleAnswer);
5843
+ const goalAnswer = await prompts.text({
5844
+ message: "Which matters most right now? (one line \u2014 this becomes their Goal)",
5845
+ placeholder: "agree on first pilot scope by May 3",
5846
+ validate(value) {
5847
+ if (!value || !value.trim()) return "Enter one line.";
5848
+ return void 0;
5849
+ }
5850
+ });
5851
+ if (prompts.isCancel(goalAnswer)) {
5852
+ return { status: "cancelled", message: "People-first capture cancelled." };
5853
+ }
5854
+ const goalTitle = typeof goalAnswer === "string" ? goalAnswer.trim() : "";
5855
+ let businessIntent;
5856
+ if (hasBusiness && relationshipStage === "conversation") {
5857
+ const intentAnswer = await prompts.select({
5858
+ initialValue: "client",
5859
+ message: "Is this person an investor in this business, or a client of it?",
5860
+ options: [
5861
+ { value: "client", label: "Client / prospect", hint: "default" },
5862
+ { value: "investor", label: "Investor / advisor" },
5863
+ { value: "unspecified", label: "Unsure / neither" }
5864
+ ]
5865
+ });
5866
+ if (prompts.isCancel(intentAnswer)) {
5867
+ return { status: "cancelled", message: "People-first capture cancelled." };
5868
+ }
5869
+ businessIntent = intentAnswer;
5870
+ }
5871
+ let businessId;
5872
+ if (hasBusiness && businessName) {
5873
+ const businessRes = await postJson(auth, "/v1/businesses", {
5874
+ display_name: businessName,
5875
+ relationship_stage: relationshipStage === "alumni" ? "alumni" : "engaged"
5876
+ });
5877
+ if (!businessRes.ok) {
5878
+ return {
5879
+ status: "failed",
5880
+ message: `${BACKEND_UNREACHABLE_HINT} (businesses: ${businessRes.error})`,
5881
+ error: businessRes.error
5882
+ };
5883
+ }
5884
+ businessId = extractId(businessRes.body) ?? void 0;
5885
+ }
5886
+ const personPayload = {
5887
+ display_name: displayName,
5888
+ headline,
5889
+ relationship_stage: relationshipStage,
5890
+ contact_channels: contactChannels,
5891
+ metadata: {
5892
+ captured_via: "orgx-wizard",
5893
+ inner_circle: innerCircle,
5894
+ trust_tier: trustTier,
5895
+ ...businessIntent ? { business_intent: businessIntent } : {}
5896
+ },
5897
+ ...businessId ? { business_id: businessId } : {}
5898
+ };
5899
+ const personRes = await postJson(auth, "/v1/people", personPayload);
5900
+ if (!personRes.ok) {
5901
+ return {
5902
+ status: "failed",
5903
+ message: `${BACKEND_UNREACHABLE_HINT} (people: ${personRes.error})`,
5904
+ error: personRes.error
5905
+ };
5906
+ }
5907
+ const personId = extractId(personRes.body) ?? void 0;
5908
+ if (!personId) {
5909
+ return {
5910
+ status: "failed",
5911
+ message: `${BACKEND_UNREACHABLE_HINT} (people: missing id in response)`,
5912
+ error: "missing_person_id"
5913
+ };
5914
+ }
5915
+ const goalRes = await postJson(auth, "/v1/goals", {
5916
+ owner_type: "person",
5917
+ owner_id: personId,
5918
+ title: goalTitle
5919
+ });
5920
+ if (!goalRes.ok) {
5921
+ return {
5922
+ status: "failed",
5923
+ message: `${BACKEND_UNREACHABLE_HINT} (goals: ${goalRes.error})`,
5924
+ error: goalRes.error
5925
+ };
5926
+ }
5927
+ const goalId = extractId(goalRes.body) ?? void 0;
5928
+ if (!goalId) {
5929
+ return {
5930
+ status: "failed",
5931
+ message: `${BACKEND_UNREACHABLE_HINT} (goals: missing id in response)`,
5932
+ error: "missing_goal_id"
5933
+ };
5934
+ }
5935
+ const person = {
5936
+ id: personId,
5937
+ display_name: displayName,
5938
+ headline,
5939
+ relationship_stage: relationshipStage,
5940
+ contact_channels: contactChannels,
5941
+ inner_circle: innerCircle,
5942
+ trust_tier: trustTier,
5943
+ ...businessId ? { business_id: businessId } : {},
5944
+ ...businessIntent ? { business_intent: businessIntent } : {}
5945
+ };
5946
+ const goal = {
5947
+ id: goalId,
5948
+ owner_type: "person",
5949
+ owner_id: personId,
5950
+ title: goalTitle
5951
+ };
5952
+ return {
5953
+ status: "completed",
5954
+ message: `Captured ${displayName} in your workspace.`,
5955
+ person,
5956
+ goal
5957
+ };
5958
+ }
5959
+
5960
+ // src/peopleFirst/templateSelect.ts
5961
+ function selectPeopleFirstTemplate(input) {
5962
+ const trustTier = input.trustTier ?? "cold";
5963
+ if (input.innerCircle) {
5964
+ return {
5965
+ persona: "inner_circle",
5966
+ reason: "inner-circle mark set by user",
5967
+ needsBusinessIntentPrompt: false
5968
+ };
5969
+ }
5970
+ switch (input.relationshipStage) {
5971
+ case "stranger":
5972
+ case "prospect":
5973
+ return {
5974
+ persona: "cold_outreach",
5975
+ reason: `stage=${input.relationshipStage} \u2014 cold outreach draft`,
5976
+ needsBusinessIntentPrompt: false
5977
+ };
5978
+ case "conversation": {
5979
+ if (input.hasBusiness) {
5980
+ if (input.businessIntent === "investor") {
5981
+ return {
5982
+ persona: "investor_prep",
5983
+ reason: "conversation + business + investor intent",
5984
+ needsBusinessIntentPrompt: false
5985
+ };
5986
+ }
5987
+ if (input.businessIntent === "client") {
5988
+ return {
5989
+ persona: "client_trust",
5990
+ reason: "conversation + business + client intent",
5991
+ needsBusinessIntentPrompt: false
5992
+ };
5993
+ }
5994
+ return {
5995
+ persona: "client_trust",
5996
+ reason: "conversation + business \u2014 defaulting to client_trust; ask to confirm",
5997
+ needsBusinessIntentPrompt: true
5998
+ };
5999
+ }
6000
+ if (trustTier === "warm" || trustTier === "close") {
6001
+ return {
6002
+ persona: "founder_ally",
6003
+ reason: `conversation + trust=${trustTier} \u2014 founder-ally outreach`,
6004
+ needsBusinessIntentPrompt: false
6005
+ };
6006
+ }
6007
+ return {
6008
+ persona: "cold_outreach",
6009
+ reason: "conversation + cold trust \u2014 treating as cold_outreach",
6010
+ needsBusinessIntentPrompt: false
6011
+ };
6012
+ }
6013
+ case "design_partner":
6014
+ case "active_client":
6015
+ return {
6016
+ persona: "client_trust",
6017
+ reason: `stage=${input.relationshipStage} \u2014 client_trust cadence`,
6018
+ needsBusinessIntentPrompt: false
6019
+ };
6020
+ case "alumni":
6021
+ return {
6022
+ persona: "alumni_touch",
6023
+ reason: "stage=alumni \u2014 alumni_touch rekindle",
6024
+ needsBusinessIntentPrompt: false
6025
+ };
6026
+ case "paused":
6027
+ case "churned":
6028
+ return {
6029
+ persona: "alumni_touch",
6030
+ reason: `stage=${input.relationshipStage} \u2014 treating as alumni_touch for rekindle tone`,
6031
+ needsBusinessIntentPrompt: false
6032
+ };
6033
+ }
6034
+ }
6035
+
6036
+ // src/lib/people-first-artifact.ts
6037
+ async function callDraftEndpoint(auth, body) {
6038
+ try {
6039
+ const res = await fetch(buildOrgxApiUrl("/v1/artifacts/draft", auth.baseUrl), {
6040
+ method: "POST",
6041
+ headers: {
6042
+ Authorization: `Bearer ${auth.apiKey}`,
6043
+ "Content-Type": "application/json"
6044
+ },
6045
+ body: JSON.stringify(body),
6046
+ signal: AbortSignal.timeout(1e4)
6047
+ });
6048
+ if (!res.ok) {
6049
+ return { ok: false, error: `HTTP ${res.status}` };
6050
+ }
6051
+ const data = await res.json().catch(() => null);
6052
+ return { ok: true, data: data ?? {} };
6053
+ } catch (err) {
6054
+ const message = err instanceof Error ? err.message : String(err);
6055
+ return { ok: false, error: message };
6056
+ }
6057
+ }
6058
+ async function runPeopleFirstArtifactDraft(options) {
6059
+ if (!options.interactive) {
6060
+ return {
6061
+ status: "skipped_non_interactive",
6062
+ message: "Artifact draft skipped \u2014 not attached to a TTY."
6063
+ };
6064
+ }
6065
+ const auth = await resolveOrgxAuth();
6066
+ if (!auth) {
6067
+ return {
6068
+ status: "failed",
6069
+ message: "Artifact draft needs OrgX auth.",
6070
+ error: "no_auth"
6071
+ };
6072
+ }
6073
+ let businessIntent = options.businessIntent;
6074
+ const { prompts, person } = options;
6075
+ const hasBusiness = Boolean(options.businessName || person.business_id);
6076
+ const preselection = selectPeopleFirstTemplate({
6077
+ relationshipStage: person.relationship_stage,
6078
+ hasBusiness,
6079
+ trustTier: person.trust_tier,
6080
+ innerCircle: person.inner_circle,
6081
+ ...businessIntent ? { businessIntent } : {}
6082
+ });
6083
+ let persona = preselection.persona;
6084
+ if (preselection.needsBusinessIntentPrompt && !businessIntent) {
6085
+ const answer = await prompts.select({
6086
+ initialValue: "client",
6087
+ message: "Is this more of a client-trust update, or investor-prep?",
6088
+ options: [
6089
+ { value: "client", label: "Client trust (default)" },
6090
+ { value: "investor", label: "Investor prep" }
6091
+ ]
6092
+ });
6093
+ if (prompts.isCancel(answer)) {
6094
+ return { status: "cancelled", message: "Artifact draft cancelled." };
6095
+ }
6096
+ businessIntent = answer;
6097
+ persona = selectPeopleFirstTemplate({
6098
+ relationshipStage: person.relationship_stage,
6099
+ hasBusiness,
6100
+ trustTier: person.trust_tier,
6101
+ innerCircle: person.inner_circle,
6102
+ businessIntent
6103
+ }).persona;
6104
+ }
6105
+ const serverResult = await callDraftEndpoint(auth, {
6106
+ template_persona: persona,
6107
+ person_id: person.id,
6108
+ calibration_inputs: {
6109
+ voice_examples: []
6110
+ }
6111
+ });
6112
+ if (!serverResult.ok) {
6113
+ return {
6114
+ status: "failed",
6115
+ message: `Could not draft artifact \u2014 ${serverResult.error}. Check your network / run \`orgx-wizard status\`.`,
6116
+ persona,
6117
+ error: serverResult.error
6118
+ };
6119
+ }
6120
+ return {
6121
+ status: "drafted",
6122
+ message: `Drafted ${persona} artifact for ${person.display_name}.`,
6123
+ persona,
6124
+ ...serverResult.data.artifact_id ? { serverArtifactId: serverResult.data.artifact_id } : {},
6125
+ ...serverResult.data.url ? { serverArtifactUrl: serverResult.data.url } : {}
6126
+ };
6127
+ }
6128
+
5528
6129
  // src/spinner.ts
5529
6130
  import ora from "ora";
5530
6131
  import pc2 from "picocolors";
@@ -6174,6 +6775,109 @@ async function maybeInstallOptionalCompanionPlugins(input) {
6174
6775
  ...input.telemetry ? { telemetry: input.telemetry } : {}
6175
6776
  });
6176
6777
  }
6778
+ async function maybeRunPeopleFirstCapture(input) {
6779
+ if (!input.interactive || !input.workspace) {
6780
+ return "skipped";
6781
+ }
6782
+ const alreadyCompleted = hasPeopleFirstCaptureCompleted(input.workspace.id);
6783
+ const captureResult = await runPeopleFirstCapture({
6784
+ interactive: input.interactive,
6785
+ workspace: input.workspace,
6786
+ alreadyCompleted,
6787
+ prompts: {
6788
+ cancel: clack.cancel,
6789
+ isCancel: clack.isCancel,
6790
+ text: textPrompt,
6791
+ select: selectPrompt,
6792
+ confirm: clack.confirm
6793
+ }
6794
+ });
6795
+ switch (captureResult.status) {
6796
+ case "skipped_already_completed":
6797
+ return "skipped";
6798
+ case "skipped_non_interactive":
6799
+ case "skipped_no_workspace":
6800
+ return "skipped";
6801
+ case "skipped_declined":
6802
+ console.log(` ${ICON.skip} ${pc3.dim(captureResult.message)}`);
6803
+ return "skipped";
6804
+ case "cancelled":
6805
+ return "cancelled";
6806
+ case "failed":
6807
+ console.log(` ${ICON.warn} ${pc3.yellow("people-first")} ${pc3.dim(captureResult.message)}`);
6808
+ return "failed";
6809
+ case "completed":
6810
+ break;
6811
+ }
6812
+ if (!captureResult.person || !captureResult.goal) {
6813
+ return "failed";
6814
+ }
6815
+ console.log(
6816
+ ` ${ICON.ok} ${pc3.green("people-first")} ${pc3.dim(
6817
+ `Captured ${captureResult.person.display_name} (${captureResult.person.relationship_stage}).`
6818
+ )}`
6819
+ );
6820
+ const draftResult = await runPeopleFirstArtifactDraft({
6821
+ interactive: input.interactive,
6822
+ person: captureResult.person,
6823
+ prompts: {
6824
+ cancel: clack.cancel,
6825
+ isCancel: clack.isCancel,
6826
+ text: textPrompt,
6827
+ select: selectPrompt,
6828
+ confirm: clack.confirm
6829
+ }
6830
+ });
6831
+ if (draftResult.status === "cancelled") {
6832
+ } else if (draftResult.status === "failed") {
6833
+ console.log(
6834
+ ` ${ICON.warn} ${pc3.yellow("artifact draft")} ${pc3.dim(draftResult.message)}`
6835
+ );
6836
+ } else if (draftResult.status === "drafted") {
6837
+ console.log(
6838
+ ` ${ICON.ok} ${pc3.green("artifact draft")} ${pc3.dim(
6839
+ `OrgX pre-drafted a ${draftResult.persona ?? "first-touch"} artifact.`
6840
+ )}`
6841
+ );
6842
+ }
6843
+ try {
6844
+ recordPeopleFirstCaptureCompletion({
6845
+ workspaceId: input.workspace.id,
6846
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
6847
+ ...captureResult.person ? { personId: captureResult.person.id } : {},
6848
+ ...draftResult.persona ? { templatePersona: draftResult.persona } : {}
6849
+ });
6850
+ } catch {
6851
+ }
6852
+ const baseUrl = process.env.ORGX_APP_URL?.trim() || DEFAULT_ORGX_BASE_URL;
6853
+ const commandUrl = `${baseUrl.replace(/\/+$/, "")}/command`;
6854
+ console.log("");
6855
+ console.log(
6856
+ ` ${ICON.ok} ${pc3.bold(
6857
+ `You brought in ${captureResult.person.display_name}.`
6858
+ )} ${pc3.dim(
6859
+ `Head to ${commandUrl} to see them in your people list \u2014 OrgX already drafted a first-touch artifact.`
6860
+ )}`
6861
+ );
6862
+ if (input.openInBrowser) {
6863
+ const openResult = openBrowser(commandUrl);
6864
+ if (!openResult.ok && openResult.error) {
6865
+ console.log(` ${pc3.dim(`(Could not auto-open browser: ${openResult.error})`)}`);
6866
+ }
6867
+ } else {
6868
+ const openAnswer = await clack.confirm({
6869
+ message: `Open ${commandUrl} in your browser now?`,
6870
+ initialValue: true
6871
+ });
6872
+ if (!clack.isCancel(openAnswer) && openAnswer === true) {
6873
+ const openResult = openBrowser(commandUrl);
6874
+ if (!openResult.ok && openResult.error) {
6875
+ console.log(` ${pc3.dim(`(Could not auto-open browser: ${openResult.error})`)}`);
6876
+ }
6877
+ }
6878
+ }
6879
+ return "completed";
6880
+ }
6177
6881
  function printAuthStatus(status) {
6178
6882
  if (!status.configured) {
6179
6883
  console.log(` ${ICON.warn} ${pc3.yellow("no account")} run ${pc3.cyan(`${getCmd()} auth login`)} to connect`);
@@ -6252,12 +6956,12 @@ function printDoctorReport(report, assessment) {
6252
6956
  async function main() {
6253
6957
  const program = new Command();
6254
6958
  program.name("orgx-wizard").description("Add OrgX MCP configs, skills/rules, and companion plugins to your local AI tools.").showHelpAfterError();
6255
- const pkgVersion = true ? "0.1.17" : void 0;
6959
+ const pkgVersion = true ? "0.1.20" : void 0;
6256
6960
  program.version(pkgVersion ?? "unknown", "-V, --version");
6257
6961
  program.hook("preAction", () => {
6258
6962
  console.log(renderBanner(pkgVersion));
6259
6963
  });
6260
- program.command("setup").description("Add OrgX MCP configs, skills/rules, and companion plugins to detected tools.").option("--preset <name>", "run a setup bundle (currently: founder)").action(async (options) => {
6964
+ program.command("setup").description("Add OrgX MCP configs, skills/rules, and companion plugins to detected tools.").option("--preset <name>", "run a setup bundle (currently: founder)").option("--open", "After people-first capture, auto-open the OrgX dashboard in your browser.", false).action(async (options) => {
6261
6965
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
6262
6966
  await safeTrackWizardTelemetry("wizard_started", {
6263
6967
  command: "setup",
@@ -6430,6 +7134,22 @@ async function main() {
6430
7134
  if (workspaceSetup.status === "cancelled") {
6431
7135
  return;
6432
7136
  }
7137
+ if (workspaceSetup.status === "reauth_requested") {
7138
+ console.log("");
7139
+ console.log(
7140
+ ` ${ICON.warn} ${pc3.yellow("workspace")} ${pc3.dim("API key authenticated but returned 0 workspaces \u2014 likely an identity mismatch.")}`
7141
+ );
7142
+ console.log(` ${pc3.cyan(`${getCmd()} auth login`)} ${pc3.dim("\u2192 re-link via OAuth, then rerun")} ${pc3.cyan(`${getCmd()} setup`)}`);
7143
+ await safeTrackWizardTelemetry(
7144
+ "workspace_bootstrapped",
7145
+ buildWorkspaceSetupTelemetryProperties(workspaceSetup, {
7146
+ command: "setup",
7147
+ interactive,
7148
+ preset: "standard"
7149
+ })
7150
+ );
7151
+ return;
7152
+ }
6433
7153
  await safeTrackWizardTelemetry(
6434
7154
  "workspace_bootstrapped",
6435
7155
  buildWorkspaceSetupTelemetryProperties(workspaceSetup, {
@@ -6463,6 +7183,14 @@ async function main() {
6463
7183
  } else if (briefResult.status === "failed") {
6464
7184
  console.log(` ${ICON.warn} ${pc3.yellow("daily brief")} ${pc3.dim(briefResult.message)}`);
6465
7185
  }
7186
+ const peopleFirstResult = await maybeRunPeopleFirstCapture({
7187
+ interactive,
7188
+ openInBrowser: Boolean(options.open),
7189
+ workspace: resolvedWorkspace
7190
+ });
7191
+ if (peopleFirstResult === "cancelled") {
7192
+ return;
7193
+ }
6466
7194
  const addOnResult = await maybeConfigureOptionalWorkspaceAddOns({
6467
7195
  interactive,
6468
7196
  telemetry: { command: "setup", preset: "standard" },