create-kanojo 0.0.0 → 0.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 (3) hide show
  1. package/README.md +3 -9
  2. package/dist/index.mjs +186 -49
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,13 +1,7 @@
1
1
  # create-kanojo
2
2
 
3
- 男性ユーザー向けの CLI 入口です。実装本体は `@repo/shared` にあり、この package は以下の preset で起動します。
3
+ Create your girlfriend with `create-kanojo`. If you want to create your boyfriend, please use `create-kareshi` instead.
4
4
 
5
- - `brand: create-kanojo`
6
- - `role: male`
7
- - `target: female`
5
+ ## Usage
8
6
 
9
- ```bash
10
- bun packages/create-kanojo/src/index.ts
11
- ```
12
-
13
- 公開用 bundle は `bun run --filter create-kanojo build` で `dist/index.mjs` に出力されます。
7
+ See https://github.com/nakasyou/matching-app
package/dist/index.mjs CHANGED
@@ -9630,7 +9630,13 @@ function createMatchingCli(preset, options = {}) {
9630
9630
  Wt(theme.banner(` ${preset.brand} `));
9631
9631
  try {
9632
9632
  const parsed = extractProfileOverride(rawArgs);
9633
- await dispatchCommand(parsed.args, parsed.profileName);
9633
+ const command = parseCommandFlags(parsed.args);
9634
+ const [rootCommand] = command.positionals;
9635
+ if (command.flags.help || rootCommand === "help") {
9636
+ Vt(renderCliUsage(preset), "Usage");
9637
+ return;
9638
+ }
9639
+ await dispatchCommand(command, parsed.profileName);
9634
9640
  Gt(theme.accent("Connected quietly. Ready for the next good match."));
9635
9641
  } catch (error) {
9636
9642
  if (error instanceof CancelledFlowError) {
@@ -9642,18 +9648,18 @@ function createMatchingCli(preset, options = {}) {
9642
9648
  service.close();
9643
9649
  }
9644
9650
  } };
9645
- async function dispatchCommand(args, profileOverride) {
9646
- const [command, subcommand, ...rest] = args;
9651
+ async function dispatchCommand(parsedArgs, profileOverride) {
9652
+ const [command, subcommand, ...rest] = parsedArgs.positionals;
9647
9653
  if (!command) {
9648
9654
  await runHome(profileOverride);
9649
9655
  return;
9650
9656
  }
9651
9657
  if (command === "profile") {
9652
- await runProfileCommand(subcommand, rest, profileOverride);
9658
+ await runProfileCommand(subcommand, rest, parsedArgs.flags, profileOverride);
9653
9659
  return;
9654
9660
  }
9655
9661
  if (command === "listing") {
9656
- await runListingCommand(subcommand, rest, profileOverride);
9662
+ await runListingCommand(subcommand, rest, parsedArgs.flags, profileOverride);
9657
9663
  return;
9658
9664
  }
9659
9665
  if (command === "discover") {
@@ -9669,11 +9675,11 @@ function createMatchingCli(preset, options = {}) {
9669
9675
  return;
9670
9676
  }
9671
9677
  if (command === "chat") {
9672
- await runChat(profileOverride, subcommand);
9678
+ await runChat(profileOverride, subcommand, parsedArgs.flags);
9673
9679
  return;
9674
9680
  }
9675
9681
  if (command === "config") {
9676
- await runConfigCommand(subcommand, profileOverride);
9682
+ await runConfigCommand(subcommand, parsedArgs.flags, profileOverride);
9677
9683
  return;
9678
9684
  }
9679
9685
  throw new Error(`Unknown command: ${command}`);
@@ -9761,9 +9767,20 @@ function createMatchingCli(preset, options = {}) {
9761
9767
  return;
9762
9768
  }
9763
9769
  }
9764
- async function runProfileCommand(subcommand, args, profileOverride) {
9770
+ async function runProfileCommand(subcommand, args, flags, profileOverride) {
9765
9771
  if (subcommand === "create") {
9766
- await promptProfileCreate();
9772
+ await promptProfileCreate({
9773
+ profileName: getStringFlag(flags, "name", "profile-name"),
9774
+ displayName: getStringFlag(flags, "display-name"),
9775
+ ageRange: getStringFlag(flags, "age-range"),
9776
+ region: getStringFlag(flags, "region"),
9777
+ bio: getStringFlag(flags, "bio"),
9778
+ interests: getStringFlag(flags, "interests"),
9779
+ lookingForAge: getStringFlag(flags, "looking-age", "looking-age-range"),
9780
+ lookingForRegions: getStringFlag(flags, "looking-regions"),
9781
+ lookingForNotes: getStringFlag(flags, "looking-notes"),
9782
+ relays: getStringFlag(flags, "relays")
9783
+ });
9767
9784
  return;
9768
9785
  }
9769
9786
  if (subcommand === "use") {
@@ -9786,10 +9803,15 @@ function createMatchingCli(preset, options = {}) {
9786
9803
  }
9787
9804
  throw new Error("Use `profile create|use|list|show`.");
9788
9805
  }
9789
- async function runListingCommand(subcommand, _args, profileOverride) {
9806
+ async function runListingCommand(subcommand, _args, flags, profileOverride) {
9790
9807
  const profile = await ensureProfile(profileOverride);
9791
9808
  if (subcommand === "publish") {
9792
- await handlePublishListing(profile);
9809
+ await handlePublishListing(profile, {
9810
+ headline: getStringFlag(flags, "title", "headline"),
9811
+ summary: getStringFlag(flags, "summary"),
9812
+ region: getStringFlag(flags, "region"),
9813
+ desiredTags: getStringFlag(flags, "tags", "desired-tags")
9814
+ });
9793
9815
  return;
9794
9816
  }
9795
9817
  if (subcommand === "list") {
@@ -9805,7 +9827,11 @@ function createMatchingCli(preset, options = {}) {
9805
9827
  R.info("There are no open listings to close.");
9806
9828
  return;
9807
9829
  }
9808
- const listingId = await askSelect({
9830
+ const listingIdArg = getStringFlag(flags, "id", "listing-id");
9831
+ const listingAddressArg = getStringFlag(flags, "address", "listing-address");
9832
+ const selectedListing = openListings.find((listing) => listing.id === listingIdArg || listing.address === listingAddressArg) ?? null;
9833
+ if ((listingIdArg || listingAddressArg) && !selectedListing) throw new Error("Listing not found. Use `listing list` to check the id or address.");
9834
+ const listingId = selectedListing?.id ?? await askSelect({
9809
9835
  message: "Choose a listing to close.",
9810
9836
  options: openListings.map((listing) => ({
9811
9837
  value: listing.id,
@@ -9829,17 +9855,17 @@ function createMatchingCli(preset, options = {}) {
9829
9855
  async function runMatches(profileOverride) {
9830
9856
  await handleMatches(await ensureProfile(profileOverride));
9831
9857
  }
9832
- async function runChat(profileOverride, matchIdArg) {
9833
- await handleChat(await ensureProfile(profileOverride), matchIdArg);
9858
+ async function runChat(profileOverride, matchIdArg, flags = {}) {
9859
+ await handleChat(await ensureProfile(profileOverride), matchIdArg, void 0, getStringFlag(flags, "message"));
9834
9860
  }
9835
- async function runConfigCommand(subcommand, profileOverride) {
9861
+ async function runConfigCommand(subcommand, flags, profileOverride) {
9836
9862
  const profile = await ensureProfile(profileOverride);
9837
9863
  if (subcommand === "show") {
9838
9864
  Vt(renderProfileCard(profile, true), "Advanced Config");
9839
9865
  return;
9840
9866
  }
9841
9867
  if (subcommand === "relays") {
9842
- const nextProfile = await promptRelayConfig(profile);
9868
+ const nextProfile = await promptRelayConfig(profile, getStringFlag(flags, "relays"));
9843
9869
  await store.saveProfile(nextProfile);
9844
9870
  R.success("Relay list updated.");
9845
9871
  return;
@@ -9863,8 +9889,8 @@ function createMatchingCli(preset, options = {}) {
9863
9889
  if (profiles.length > 1) return promptProfileUse();
9864
9890
  return promptProfileCreate();
9865
9891
  }
9866
- async function promptProfileCreate() {
9867
- const profileName = await askText({
9892
+ async function promptProfileCreate(initial = {}) {
9893
+ const profileName = await resolveRequiredInput(initial.profileName, {
9868
9894
  message: "Choose a profile name.",
9869
9895
  placeholder: "main",
9870
9896
  defaultValue: "main",
@@ -9874,50 +9900,51 @@ function createMatchingCli(preset, options = {}) {
9874
9900
  if (!/^[a-z0-9-]+$/.test(normalized)) return "Use lowercase letters, digits, and hyphens only.";
9875
9901
  }
9876
9902
  });
9877
- const displayName = await askText({
9903
+ const displayName = await resolveRequiredInput(initial.displayName, {
9878
9904
  message: "What display name should we show?",
9879
9905
  placeholder: preset.brand === "create-kanojo" ? "たくみ" : "あや",
9880
9906
  validate(value) {
9881
9907
  if (!(value?.trim() ?? "")) return "Display name is required.";
9882
9908
  }
9883
9909
  });
9884
- const ageRange = await askText({
9910
+ const ageRange = await resolveRequiredInput(initial.ageRange, {
9885
9911
  message: "How would you describe your age range?",
9886
9912
  placeholder: "20代後半",
9887
9913
  validate(value) {
9888
9914
  if (!(value?.trim() ?? "")) return "Age range is required.";
9889
9915
  }
9890
9916
  });
9891
- const region = await askText({
9917
+ const region = await resolveRequiredInput(initial.region, {
9892
9918
  message: "Which area do you usually meet in?",
9893
9919
  placeholder: "東京",
9894
9920
  validate(value) {
9895
9921
  if (!(value?.trim() ?? "")) return "Region is required.";
9896
9922
  }
9897
9923
  });
9898
- const bio = await askText({
9924
+ const bio = await resolveRequiredInput(initial.bio, {
9899
9925
  message: "Write a short intro.",
9900
9926
  placeholder: "映画とコーヒーが好きです。",
9901
9927
  validate(value) {
9902
9928
  if (!(value?.trim() ?? "")) return "Bio is required.";
9903
9929
  }
9904
9930
  });
9905
- const interests = await askText({
9931
+ const interests = await resolveOptionalInput(initial.interests, {
9906
9932
  message: "List interests or vibe tags, comma separated.",
9907
9933
  placeholder: "映画, カフェ, 散歩"
9908
9934
  });
9909
- const lookingForAge = await askText({
9935
+ const lookingForAge = await resolveOptionalInput(initial.lookingForAge, {
9910
9936
  message: "What age range are you looking for?",
9911
9937
  placeholder: "20代"
9912
9938
  });
9913
- const lookingForRegions = await askText({
9939
+ const lookingForRegions = await resolveOptionalInput(initial.lookingForRegions, {
9914
9940
  message: "Which regions do you want to meet in? Use commas.",
9915
9941
  placeholder: region
9916
9942
  });
9917
- const lookingForNotes = await askText({
9943
+ const lookingForNotes = await resolveOptionalInput(initial.lookingForNotes, {
9918
9944
  message: "What kind of person feels right for you?",
9919
9945
  placeholder: "落ち着いて話せる人"
9920
9946
  });
9947
+ const relays = initial.relays ? normalizeRelayList(initial.relays) : DEFAULT_RELAYS;
9921
9948
  const credentials = createGeneratedCredentials();
9922
9949
  const profile = createDefaultProfile(preset, {
9923
9950
  profileName,
@@ -9932,7 +9959,7 @@ function createMatchingCli(preset, options = {}) {
9932
9959
  notes: lookingForNotes
9933
9960
  },
9934
9961
  nostr: credentials,
9935
- relays: DEFAULT_RELAYS
9962
+ relays
9936
9963
  });
9937
9964
  const published = await withSpinner("Preparing profile...", async () => {
9938
9965
  await service.publishProfile(profile);
@@ -9964,26 +9991,26 @@ function createMatchingCli(preset, options = {}) {
9964
9991
  Vt(renderProfileCard(profile), "Active Profile");
9965
9992
  return profile;
9966
9993
  }
9967
- async function handlePublishListing(profile) {
9968
- const headline = await askText({
9994
+ async function handlePublishListing(profile, initial = {}) {
9995
+ const headline = await resolveRequiredInput(initial.headline, {
9969
9996
  message: "Enter the listing title.",
9970
9997
  placeholder: "週末に一緒に映画を見に行ける人",
9971
9998
  validate(value) {
9972
9999
  if (!(value?.trim() ?? "")) return "Title is required.";
9973
10000
  }
9974
10001
  });
9975
- const summary = await askText({
10002
+ const summary = await resolveRequiredInput(initial.summary, {
9976
10003
  message: "Write a short summary.",
9977
10004
  placeholder: "まずはお茶からゆっくり話したいです。",
9978
10005
  validate(value) {
9979
10006
  if (!(value?.trim() ?? "")) return "Summary is required.";
9980
10007
  }
9981
10008
  });
9982
- const region = await askText({
10009
+ const region = await resolveOptionalInput(initial.region, {
9983
10010
  message: "Which region is this listing for?",
9984
10011
  defaultValue: profile.region
9985
10012
  });
9986
- const desiredTags = await askText({
10013
+ const desiredTags = await resolveOptionalInput(initial.desiredTags, {
9987
10014
  message: "Enter tags, comma separated.",
9988
10015
  placeholder: "映画, 落ち着き, 夜カフェ"
9989
10016
  });
@@ -10092,7 +10119,7 @@ function createMatchingCli(preset, options = {}) {
10092
10119
  })) return handleChat(nextProfile, void 0, buildConversations(nextProfile).filter((conversation) => conversation.source === "matched"));
10093
10120
  return nextProfile;
10094
10121
  }
10095
- async function handleChat(profile, threadIdArg, availableConversations) {
10122
+ async function handleChat(profile, threadIdArg, availableConversations, initialMessage) {
10096
10123
  let nextProfile = profile;
10097
10124
  if (!availableConversations) {
10098
10125
  nextProfile = await withSpinner("Syncing conversation...", () => service.syncInbox(profile));
@@ -10105,6 +10132,11 @@ function createMatchingCli(preset, options = {}) {
10105
10132
  }
10106
10133
  const conversation = (threadIdArg ? conversations.find((item) => item.threadId === threadIdArg) : null) ?? await promptConversation(conversations);
10107
10134
  if (!conversation) throw new Error("Conversation not found.");
10135
+ if (initialMessage !== void 0) {
10136
+ const body = initialMessage.trim();
10137
+ if (!body) throw new Error("Message is required.");
10138
+ return sendChatMessage(nextProfile, conversation, body);
10139
+ }
10108
10140
  Vt(renderConversation(conversation), `chat | ${conversation.peerProfileName}`);
10109
10141
  while (true) {
10110
10142
  const body = await askText({
@@ -10113,21 +10145,26 @@ function createMatchingCli(preset, options = {}) {
10113
10145
  defaultValue: ""
10114
10146
  });
10115
10147
  if (!body.trim()) return nextProfile;
10116
- nextProfile = await withSpinner("Sending message...", () => service.sendChat(nextProfile, {
10117
- matchId: conversation.threadId,
10118
- recipientPubkey: conversation.peerPubkey,
10119
- recipientRelays: conversation.peerRelays,
10120
- body
10121
- }));
10122
- await store.saveProfile(nextProfile);
10123
- const refreshedConversation = buildConversations(nextProfile).find((item) => item.threadId === conversation.threadId);
10124
- if (refreshedConversation) Vt(renderConversation(refreshedConversation), `chat | ${refreshedConversation.peerProfileName}`);
10148
+ nextProfile = await sendChatMessage(nextProfile, conversation, body);
10125
10149
  if (!await askConfirm({
10126
10150
  message: "Send another message?",
10127
10151
  initialValue: false
10128
10152
  })) return nextProfile;
10129
10153
  }
10130
10154
  }
10155
+ async function sendChatMessage(profile, conversation, body) {
10156
+ const nextProfile = await withSpinner("Sending message...", () => service.sendChat(profile, {
10157
+ matchId: conversation.threadId,
10158
+ recipientPubkey: conversation.peerPubkey,
10159
+ recipientRelays: conversation.peerRelays,
10160
+ body
10161
+ }));
10162
+ await store.saveProfile(nextProfile);
10163
+ const refreshedConversation = buildConversations(nextProfile).find((item) => item.threadId === conversation.threadId);
10164
+ if (refreshedConversation) Vt(renderConversation(refreshedConversation), `chat | ${refreshedConversation.peerProfileName}`);
10165
+ R.success("Message sent.");
10166
+ return nextProfile;
10167
+ }
10131
10168
  async function promptConfig(profile) {
10132
10169
  const action = await askSelect({
10133
10170
  message: "Advanced config",
@@ -10155,19 +10192,20 @@ function createMatchingCli(preset, options = {}) {
10155
10192
  if (action === "relays") return promptRelayConfig(profile);
10156
10193
  return profile;
10157
10194
  }
10158
- async function promptRelayConfig(profile) {
10195
+ async function promptRelayConfig(profile, relayInputArg) {
10159
10196
  Vt(profile.relays.join("\n"), "Current Relays");
10160
- const relayInput = await askText({
10197
+ const relayInput = relayInputArg ?? await askText({
10161
10198
  message: "Enter new relays, comma separated.",
10162
10199
  defaultValue: profile.relays.join(", "),
10163
10200
  validate(value) {
10164
- const items = splitComma(value ?? "");
10165
- if (items.length === 0) return "At least one relay is required.";
10166
- if (items.length > 3) return "Use up to 3 relays.";
10167
- if (!items.every((item) => item.startsWith("wss://"))) return "Relay URLs must start with wss://.";
10201
+ try {
10202
+ normalizeRelayList(value ?? "");
10203
+ } catch (error) {
10204
+ return error instanceof Error ? error.message : "Invalid relay list.";
10205
+ }
10168
10206
  }
10169
10207
  });
10170
- const nextProfile = await withSpinner("Updating relays...", () => service.updateRelays(profile, splitComma(relayInput)));
10208
+ const nextProfile = await withSpinner("Updating relays...", () => service.updateRelays(profile, normalizeRelayList(relayInput)));
10171
10209
  await store.saveProfile(nextProfile);
10172
10210
  return nextProfile;
10173
10211
  }
@@ -10180,6 +10218,31 @@ function createTheme(preset) {
10180
10218
  banner: (label) => accent(import_picocolors.default.inverse(label))
10181
10219
  };
10182
10220
  }
10221
+ function renderCliUsage(preset) {
10222
+ const sampleDisplayName = preset.brand === "create-kanojo" ? "たくみ" : "あや";
10223
+ return [
10224
+ `${preset.brand} [command] [options]`,
10225
+ "",
10226
+ "Interactive",
10227
+ ` ${preset.brand}`,
10228
+ "",
10229
+ "Quick commands",
10230
+ " profile list",
10231
+ " profile use <name>",
10232
+ " profile show",
10233
+ " profile create --name main --display-name \"<name>\" --age-range \"20代後半\" --region 東京 --bio \"映画とコーヒーが好きです。\" --interests \"映画, カフェ\" --looking-age \"20代\" --looking-regions \"東京, 神奈川\" --looking-notes \"落ち着いて話せる人\"",
10234
+ " listing publish --title \"週末に一緒に映画を見に行ける人\" --summary \"まずはお茶からゆっくり話したいです。\" --region 東京 --tags \"映画, 夜カフェ\"",
10235
+ " listing close --id <listing-id>",
10236
+ " chat <thread-id> --message \"こんにちは\"",
10237
+ " config relays --relays \"wss://relay1.example,wss://relay2.example\"",
10238
+ "",
10239
+ "Global options",
10240
+ " --profile <name> Use a specific profile",
10241
+ " --help Show this help",
10242
+ "",
10243
+ `Example: ${preset.brand} profile create --name main --display-name "${sampleDisplayName}" --age-range "20代後半" --region 東京 --bio "映画とコーヒーが好きです。"`
10244
+ ].join("\n");
10245
+ }
10183
10246
  function extractProfileOverride(args) {
10184
10247
  const remaining = [];
10185
10248
  let profileName = null;
@@ -10197,6 +10260,48 @@ function extractProfileOverride(args) {
10197
10260
  profileName
10198
10261
  };
10199
10262
  }
10263
+ function parseCommandFlags(args) {
10264
+ const positionals = [];
10265
+ const flags = {};
10266
+ for (let index = 0; index < args.length; index += 1) {
10267
+ const value = args[index] ?? "";
10268
+ if (!value.startsWith("-") || value === "-") {
10269
+ positionals.push(value);
10270
+ continue;
10271
+ }
10272
+ if (value === "--") {
10273
+ positionals.push(...args.slice(index + 1));
10274
+ break;
10275
+ }
10276
+ if (value === "-h") {
10277
+ flags.help = true;
10278
+ continue;
10279
+ }
10280
+ if (!value.startsWith("--")) {
10281
+ positionals.push(value);
10282
+ continue;
10283
+ }
10284
+ const trimmed = value.slice(2);
10285
+ const separatorIndex = trimmed.indexOf("=");
10286
+ if (separatorIndex >= 0) {
10287
+ const name = trimmed.slice(0, separatorIndex).toLowerCase();
10288
+ flags[name] = trimmed.slice(separatorIndex + 1);
10289
+ continue;
10290
+ }
10291
+ const name = trimmed.toLowerCase();
10292
+ const next = args[index + 1];
10293
+ if (next && !next.startsWith("-")) {
10294
+ flags[name] = next;
10295
+ index += 1;
10296
+ continue;
10297
+ }
10298
+ flags[name] = true;
10299
+ }
10300
+ return {
10301
+ positionals,
10302
+ flags
10303
+ };
10304
+ }
10200
10305
  async function askText(options) {
10201
10306
  const answer = await Zt(options);
10202
10307
  if (Ct$1(answer)) throw new CancelledFlowError();
@@ -10310,6 +10415,38 @@ function renderConversation(conversation) {
10310
10415
  function splitComma(value) {
10311
10416
  return [...new Set(value.split(",").map((item) => item.trim()).filter(Boolean))];
10312
10417
  }
10418
+ function getStringFlag(flags, ...names) {
10419
+ for (const name of names) {
10420
+ const value = flags[name];
10421
+ if (typeof value === "string") return value;
10422
+ if (value === true) return "";
10423
+ }
10424
+ }
10425
+ async function resolveRequiredInput(providedValue, options) {
10426
+ if (providedValue !== void 0) {
10427
+ const normalized = providedValue.trim();
10428
+ const validation = options.validate?.(normalized);
10429
+ if (validation) throw new Error(String(validation));
10430
+ return normalized;
10431
+ }
10432
+ return askText(options);
10433
+ }
10434
+ async function resolveOptionalInput(providedValue, options) {
10435
+ if (providedValue !== void 0) {
10436
+ const normalized = providedValue.trim();
10437
+ const validation = options.validate?.(normalized);
10438
+ if (validation) throw new Error(String(validation));
10439
+ return normalized;
10440
+ }
10441
+ return askText(options);
10442
+ }
10443
+ function normalizeRelayList(value) {
10444
+ const items = splitComma(value);
10445
+ if (items.length === 0) throw new Error("At least one relay is required.");
10446
+ if (items.length > 3) throw new Error("Use up to 3 relays.");
10447
+ if (!items.every((item) => item.startsWith("wss://"))) throw new Error("Relay URLs must start with wss://.");
10448
+ return items;
10449
+ }
10313
10450
  async function askSwipeAction() {
10314
10451
  if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
10315
10452
  const normalized = normalizeSwipeAction(await askText({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kanojo",
3
- "version": "0.0.0",
3
+ "version": "0.1.1",
4
4
  "description": "A Nostr-based dating CLI for finding a kanojo.",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -27,6 +27,7 @@
27
27
  "vite-plus": "^0.1.11"
28
28
  },
29
29
  "peerDependencies": {
30
- "typescript": "^5"
30
+ "typescript": "^5",
31
+ "@repo/shared": "@repo/shared"
31
32
  }
32
33
  }