create-kanojo 0.1.1 → 0.1.3
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/index.mjs +621 -133
- package/package.json +2 -3
package/dist/index.mjs
CHANGED
|
@@ -32,7 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
32
|
enumerable: true
|
|
33
33
|
}) : target, mod));
|
|
34
34
|
//#endregion
|
|
35
|
-
//#region ../../node_modules
|
|
35
|
+
//#region ../../node_modules/@clack/core/dist/index.mjs
|
|
36
36
|
var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
37
37
|
const ESC = "\x1B";
|
|
38
38
|
const CSI = `${ESC}[`;
|
|
@@ -542,7 +542,7 @@ var $t = class extends B {
|
|
|
542
542
|
}
|
|
543
543
|
};
|
|
544
544
|
//#endregion
|
|
545
|
-
//#region ../../node_modules
|
|
545
|
+
//#region ../../node_modules/@clack/prompts/dist/index.mjs
|
|
546
546
|
function pt() {
|
|
547
547
|
return N.platform !== "win32" ? N.env.TERM !== "linux" : !!N.env.CI || !!N.env.WT_SESSION || !!N.env.TERMINUS_SUBLIME || N.env.ConEmuTask === "{cmd::Cmder}" || N.env.TERM_PROGRAM === "Terminus-Sublime" || N.env.TERM_PROGRAM === "vscode" || N.env.TERM === "xterm-256color" || N.env.TERM === "alacritty" || N.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
548
548
|
}
|
|
@@ -1062,7 +1062,7 @@ ${r ? styleText("cyan", x) : ""}
|
|
|
1062
1062
|
}
|
|
1063
1063
|
}).prompt();
|
|
1064
1064
|
//#endregion
|
|
1065
|
-
//#region ../../node_modules
|
|
1065
|
+
//#region ../../node_modules/@noble/hashes/utils.js
|
|
1066
1066
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1067
1067
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
1068
1068
|
let isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
@@ -1283,7 +1283,7 @@ const oidNist = (suffix) => ({ oid: Uint8Array.from([
|
|
|
1283
1283
|
suffix
|
|
1284
1284
|
]) });
|
|
1285
1285
|
//#endregion
|
|
1286
|
-
//#region ../../node_modules
|
|
1286
|
+
//#region ../../node_modules/@noble/hashes/_md.js
|
|
1287
1287
|
/**
|
|
1288
1288
|
* Internal Merkle-Damgard hash utils.
|
|
1289
1289
|
* @module
|
|
@@ -1404,7 +1404,7 @@ const SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
|
1404
1404
|
1541459225
|
|
1405
1405
|
]);
|
|
1406
1406
|
//#endregion
|
|
1407
|
-
//#region ../../node_modules
|
|
1407
|
+
//#region ../../node_modules/@noble/hashes/_u64.js
|
|
1408
1408
|
/**
|
|
1409
1409
|
* Internal helpers for u64. BigUint64Array is too slow as per 2025, so we implement it using Uint32Array.
|
|
1410
1410
|
* @todo re-check https://issues.chromium.org/issues/42212588
|
|
@@ -1433,7 +1433,7 @@ function split(lst, le = false) {
|
|
|
1433
1433
|
return [Ah, Al];
|
|
1434
1434
|
}
|
|
1435
1435
|
//#endregion
|
|
1436
|
-
//#region ../../node_modules
|
|
1436
|
+
//#region ../../node_modules/@noble/hashes/sha2.js
|
|
1437
1437
|
/**
|
|
1438
1438
|
* SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.
|
|
1439
1439
|
* SHA256 is the fastest hash implementable in JS, even faster than Blake3.
|
|
@@ -1689,7 +1689,7 @@ K512[1];
|
|
|
1689
1689
|
*/
|
|
1690
1690
|
const sha256 = /* @__PURE__ */ createHasher$1(() => new _SHA256(), /* @__PURE__ */ oidNist(1));
|
|
1691
1691
|
//#endregion
|
|
1692
|
-
//#region ../../node_modules
|
|
1692
|
+
//#region ../../node_modules/@noble/curves/utils.js
|
|
1693
1693
|
/**
|
|
1694
1694
|
* Hex, bytes and number utilities.
|
|
1695
1695
|
* @module
|
|
@@ -1867,7 +1867,7 @@ function memoized(fn) {
|
|
|
1867
1867
|
};
|
|
1868
1868
|
}
|
|
1869
1869
|
//#endregion
|
|
1870
|
-
//#region ../../node_modules
|
|
1870
|
+
//#region ../../node_modules/@noble/curves/abstract/modular.js
|
|
1871
1871
|
/**
|
|
1872
1872
|
* Utils for modular division and fields.
|
|
1873
1873
|
* Field over 11 is a finite (Galois) field is integer number operations `mod 11`.
|
|
@@ -2292,7 +2292,7 @@ function mapHashToField(key, fieldOrder, isLE = false) {
|
|
|
2292
2292
|
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
|
|
2293
2293
|
}
|
|
2294
2294
|
//#endregion
|
|
2295
|
-
//#region ../../node_modules
|
|
2295
|
+
//#region ../../node_modules/@noble/curves/abstract/curve.js
|
|
2296
2296
|
/**
|
|
2297
2297
|
* Methods for elliptic curve multiplication by scalars.
|
|
2298
2298
|
* Contains wNAF, pippenger.
|
|
@@ -2564,7 +2564,7 @@ function createKeygen(randomSecretKey, getPublicKey) {
|
|
|
2564
2564
|
};
|
|
2565
2565
|
}
|
|
2566
2566
|
//#endregion
|
|
2567
|
-
//#region ../../node_modules
|
|
2567
|
+
//#region ../../node_modules/@noble/curves/abstract/hash-to-curve.js
|
|
2568
2568
|
const os2ip = bytesToNumberBE;
|
|
2569
2569
|
function i2osp(value, length) {
|
|
2570
2570
|
asafenumber(value);
|
|
@@ -2726,7 +2726,7 @@ function createHasher(Point, mapToCurve, defaults) {
|
|
|
2726
2726
|
};
|
|
2727
2727
|
}
|
|
2728
2728
|
//#endregion
|
|
2729
|
-
//#region ../../node_modules
|
|
2729
|
+
//#region ../../node_modules/@noble/hashes/hmac.js
|
|
2730
2730
|
/**
|
|
2731
2731
|
* HMAC: RFC2104 message authentication code.
|
|
2732
2732
|
* @module
|
|
@@ -2809,7 +2809,7 @@ var _HMAC = class {
|
|
|
2809
2809
|
const hmac = (hash, key, message) => new _HMAC(hash, key).update(message).digest();
|
|
2810
2810
|
hmac.create = (hash, key) => new _HMAC(hash, key);
|
|
2811
2811
|
//#endregion
|
|
2812
|
-
//#region ../../node_modules
|
|
2812
|
+
//#region ../../node_modules/@noble/curves/abstract/weierstrass.js
|
|
2813
2813
|
/**
|
|
2814
2814
|
* Short Weierstrass curve methods. The formula is: y² = x³ + ax + b.
|
|
2815
2815
|
*
|
|
@@ -3860,7 +3860,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
|
3860
3860
|
});
|
|
3861
3861
|
}
|
|
3862
3862
|
//#endregion
|
|
3863
|
-
//#region ../../node_modules
|
|
3863
|
+
//#region ../../node_modules/@noble/curves/secp256k1.js
|
|
3864
3864
|
/**
|
|
3865
3865
|
* SECG secp256k1. See [pdf](https://www.secg.org/sec2-v2.pdf).
|
|
3866
3866
|
*
|
|
@@ -4102,7 +4102,7 @@ createHasher(Pointk1, (scalars) => {
|
|
|
4102
4102
|
hash: sha256
|
|
4103
4103
|
});
|
|
4104
4104
|
//#endregion
|
|
4105
|
-
//#region ../../node_modules
|
|
4105
|
+
//#region ../../node_modules/@scure/base/index.js
|
|
4106
4106
|
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
4107
4107
|
function isBytes$1(a) {
|
|
4108
4108
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
@@ -4436,7 +4436,7 @@ typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex ===
|
|
|
4436
4436
|
return s.toLowerCase();
|
|
4437
4437
|
}));
|
|
4438
4438
|
//#endregion
|
|
4439
|
-
//#region ../../node_modules
|
|
4439
|
+
//#region ../../node_modules/@noble/ciphers/utils.js
|
|
4440
4440
|
/**
|
|
4441
4441
|
* Utilities for hex, bytes, CSPRNG.
|
|
4442
4442
|
* @module
|
|
@@ -4584,7 +4584,7 @@ function copyBytes(bytes) {
|
|
|
4584
4584
|
return Uint8Array.from(bytes);
|
|
4585
4585
|
}
|
|
4586
4586
|
//#endregion
|
|
4587
|
-
//#region ../../node_modules
|
|
4587
|
+
//#region ../../node_modules/@noble/ciphers/aes.js
|
|
4588
4588
|
const BLOCK_SIZE = 16;
|
|
4589
4589
|
const POLY = 283;
|
|
4590
4590
|
function validateKeyLength(key) {
|
|
@@ -4954,7 +4954,7 @@ var _CMAC = class {
|
|
|
4954
4954
|
const cmac = (key, message) => new _CMAC(key).update(message).digest();
|
|
4955
4955
|
cmac.create = (key) => new _CMAC(key);
|
|
4956
4956
|
//#endregion
|
|
4957
|
-
//#region ../../node_modules
|
|
4957
|
+
//#region ../../node_modules/@noble/ciphers/_arx.js
|
|
4958
4958
|
/**
|
|
4959
4959
|
* Basic utils for ARX (add-rotate-xor) salsa and chacha ciphers.
|
|
4960
4960
|
|
|
@@ -5099,7 +5099,7 @@ function createCipher(core, opts) {
|
|
|
5099
5099
|
};
|
|
5100
5100
|
}
|
|
5101
5101
|
//#endregion
|
|
5102
|
-
//#region ../../node_modules
|
|
5102
|
+
//#region ../../node_modules/@noble/ciphers/_poly1305.js
|
|
5103
5103
|
/**
|
|
5104
5104
|
* Poly1305 ([PDF](https://cr.yp.to/mac/poly1305-20050329.pdf),
|
|
5105
5105
|
* [wiki](https://en.wikipedia.org/wiki/Poly1305))
|
|
@@ -5371,7 +5371,7 @@ function wrapConstructorWithKey(hashCons) {
|
|
|
5371
5371
|
/** Poly1305 MAC from RFC 8439. */
|
|
5372
5372
|
const poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
|
|
5373
5373
|
//#endregion
|
|
5374
|
-
//#region ../../node_modules
|
|
5374
|
+
//#region ../../node_modules/@noble/ciphers/chacha.js
|
|
5375
5375
|
/**
|
|
5376
5376
|
* ChaCha stream cipher, released
|
|
5377
5377
|
* in 2008. Developed after Salsa20, ChaCha aims to increase diffusion per round.
|
|
@@ -5634,7 +5634,7 @@ const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
|
|
5634
5634
|
_poly1305_aead(chacha20);
|
|
5635
5635
|
_poly1305_aead(xchacha20);
|
|
5636
5636
|
//#endregion
|
|
5637
|
-
//#region ../../node_modules
|
|
5637
|
+
//#region ../../node_modules/@noble/hashes/hkdf.js
|
|
5638
5638
|
/**
|
|
5639
5639
|
* HKDF (RFC 5869): extract + expand in one step.
|
|
5640
5640
|
* See https://soatok.blog/2021/11/17/understanding-hkdf/.
|
|
@@ -5685,7 +5685,7 @@ function expand(hash, prk, info, length = 32) {
|
|
|
5685
5685
|
return okm.slice(0, length);
|
|
5686
5686
|
}
|
|
5687
5687
|
//#endregion
|
|
5688
|
-
//#region ../../node_modules
|
|
5688
|
+
//#region ../../node_modules/nostr-tools/lib/esm/index.js
|
|
5689
5689
|
var __defProp = Object.defineProperty;
|
|
5690
5690
|
var __export = (target, all) => {
|
|
5691
5691
|
for (var name in all) __defProp(target, name, {
|
|
@@ -9318,21 +9318,31 @@ function createNostrService(options = {}) {
|
|
|
9318
9318
|
};
|
|
9319
9319
|
}
|
|
9320
9320
|
async function closeListing(profile, listingId) {
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9321
|
+
return updateListing(profile, {
|
|
9322
|
+
listingId,
|
|
9323
|
+
status: "closed"
|
|
9324
|
+
});
|
|
9325
|
+
}
|
|
9326
|
+
async function updateListing(profile, input) {
|
|
9327
|
+
const listing = profile.cache.listings.find((item) => item.id === input.listingId);
|
|
9328
|
+
if (!listing) throw new Error(`Listing not found: ${input.listingId}`);
|
|
9329
|
+
const nextListing = {
|
|
9324
9330
|
...listing,
|
|
9325
|
-
|
|
9331
|
+
headline: input.headline?.trim() ?? listing.headline,
|
|
9332
|
+
summary: input.summary?.trim() ?? listing.summary,
|
|
9333
|
+
region: input.region?.trim() ?? listing.region,
|
|
9334
|
+
desiredTags: uniqueStrings(input.desiredTags ?? listing.desiredTags),
|
|
9335
|
+
status: input.status ?? listing.status,
|
|
9326
9336
|
updatedAt: now()
|
|
9327
9337
|
};
|
|
9328
9338
|
const secretKey = decodeNsec(profile.nostr.nsec);
|
|
9329
|
-
const signed = finalizeEvent(serializeMatchingListingEvent(profile,
|
|
9339
|
+
const signed = finalizeEvent(serializeMatchingListingEvent(profile, nextListing), secretKey);
|
|
9330
9340
|
await transport.publish(profile.relays, [signed]);
|
|
9331
9341
|
return {
|
|
9332
9342
|
...profile,
|
|
9333
9343
|
cache: {
|
|
9334
9344
|
...profile.cache,
|
|
9335
|
-
listings: [
|
|
9345
|
+
listings: [nextListing, ...profile.cache.listings.filter((item) => item.id !== nextListing.id)],
|
|
9336
9346
|
lastListingSyncAt: now()
|
|
9337
9347
|
}
|
|
9338
9348
|
};
|
|
@@ -9461,6 +9471,7 @@ function createNostrService(options = {}) {
|
|
|
9461
9471
|
publishListing,
|
|
9462
9472
|
refreshOwnListings,
|
|
9463
9473
|
closeListing,
|
|
9474
|
+
updateListing,
|
|
9464
9475
|
discoverListings,
|
|
9465
9476
|
syncInbox,
|
|
9466
9477
|
sendLike,
|
|
@@ -9478,6 +9489,13 @@ function createGeneratedCredentials() {
|
|
|
9478
9489
|
nsec: nip19_exports.nsecEncode(secretKey)
|
|
9479
9490
|
};
|
|
9480
9491
|
}
|
|
9492
|
+
function importCredentials(nsec) {
|
|
9493
|
+
const secretKey = decodeNsec(nsec.trim());
|
|
9494
|
+
return {
|
|
9495
|
+
pubkey: getPublicKey(secretKey),
|
|
9496
|
+
nsec: nip19_exports.nsecEncode(secretKey)
|
|
9497
|
+
};
|
|
9498
|
+
}
|
|
9481
9499
|
function createSimplePoolTransport() {
|
|
9482
9500
|
const pool = new SimplePool();
|
|
9483
9501
|
return {
|
|
@@ -9625,29 +9643,85 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9625
9643
|
const store = loadProfileStore(options.baseDir);
|
|
9626
9644
|
const service = createNostrService(options);
|
|
9627
9645
|
const theme = createTheme(preset);
|
|
9646
|
+
let plainOutput = false;
|
|
9628
9647
|
return { async run(rawArgs = process.argv.slice(2)) {
|
|
9629
9648
|
await store.ensure();
|
|
9630
|
-
Wt(theme.banner(` ${preset.brand} `));
|
|
9631
9649
|
try {
|
|
9632
9650
|
const parsed = extractProfileOverride(rawArgs);
|
|
9633
9651
|
const command = parseCommandFlags(parsed.args);
|
|
9634
9652
|
const [rootCommand] = command.positionals;
|
|
9653
|
+
plainOutput = command.positionals.length > 0 || Boolean(command.flags.help) || !process.stdout.isTTY;
|
|
9654
|
+
showIntro();
|
|
9635
9655
|
if (command.flags.help || rootCommand === "help") {
|
|
9636
|
-
|
|
9656
|
+
showText(renderCliUsage(preset));
|
|
9637
9657
|
return;
|
|
9638
9658
|
}
|
|
9639
9659
|
await dispatchCommand(command, parsed.profileName);
|
|
9640
|
-
|
|
9660
|
+
showOutro("Connected quietly. Ready for the next good match.");
|
|
9641
9661
|
} catch (error) {
|
|
9642
9662
|
if (error instanceof CancelledFlowError) {
|
|
9643
|
-
|
|
9663
|
+
showCancelled("Operation cancelled.");
|
|
9644
9664
|
return;
|
|
9645
9665
|
}
|
|
9646
|
-
|
|
9666
|
+
showError(error instanceof Error ? error.message : "Unexpected error.");
|
|
9647
9667
|
} finally {
|
|
9648
9668
|
service.close();
|
|
9649
9669
|
}
|
|
9650
9670
|
} };
|
|
9671
|
+
function showIntro() {
|
|
9672
|
+
if (!plainOutput) Wt(theme.banner(` ${preset.brand} `));
|
|
9673
|
+
}
|
|
9674
|
+
function showOutro(message) {
|
|
9675
|
+
if (!plainOutput) Gt(theme.accent(message));
|
|
9676
|
+
}
|
|
9677
|
+
function showCancelled(message) {
|
|
9678
|
+
if (plainOutput) {
|
|
9679
|
+
process.stderr.write(`${message}\n`);
|
|
9680
|
+
return;
|
|
9681
|
+
}
|
|
9682
|
+
Nt(message);
|
|
9683
|
+
}
|
|
9684
|
+
function showText(message) {
|
|
9685
|
+
process.stdout.write(`${message}\n`);
|
|
9686
|
+
}
|
|
9687
|
+
function showSection(body, title) {
|
|
9688
|
+
if (plainOutput) {
|
|
9689
|
+
showText(`${title}\n${body}`);
|
|
9690
|
+
return;
|
|
9691
|
+
}
|
|
9692
|
+
Vt(body, title);
|
|
9693
|
+
}
|
|
9694
|
+
function showInfo(message) {
|
|
9695
|
+
if (plainOutput) {
|
|
9696
|
+
showText(message);
|
|
9697
|
+
return;
|
|
9698
|
+
}
|
|
9699
|
+
R.info(message);
|
|
9700
|
+
}
|
|
9701
|
+
function showSuccess(message) {
|
|
9702
|
+
if (plainOutput) {
|
|
9703
|
+
showText(message);
|
|
9704
|
+
return;
|
|
9705
|
+
}
|
|
9706
|
+
R.success(message);
|
|
9707
|
+
}
|
|
9708
|
+
function showStep(message) {
|
|
9709
|
+
if (plainOutput) {
|
|
9710
|
+
showText(message);
|
|
9711
|
+
return;
|
|
9712
|
+
}
|
|
9713
|
+
R.step(message);
|
|
9714
|
+
}
|
|
9715
|
+
function showError(message) {
|
|
9716
|
+
if (plainOutput) {
|
|
9717
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
9718
|
+
return;
|
|
9719
|
+
}
|
|
9720
|
+
R.error(message);
|
|
9721
|
+
}
|
|
9722
|
+
async function runWithSpinner(message, task) {
|
|
9723
|
+
return withSpinner(message, task, plainOutput);
|
|
9724
|
+
}
|
|
9651
9725
|
async function dispatchCommand(parsedArgs, profileOverride) {
|
|
9652
9726
|
const [command, subcommand, ...rest] = parsedArgs.positionals;
|
|
9653
9727
|
if (!command) {
|
|
@@ -9663,7 +9737,7 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9663
9737
|
return;
|
|
9664
9738
|
}
|
|
9665
9739
|
if (command === "discover") {
|
|
9666
|
-
await
|
|
9740
|
+
await runDiscoverCommand(subcommand, rest, parsedArgs.flags, profileOverride);
|
|
9667
9741
|
return;
|
|
9668
9742
|
}
|
|
9669
9743
|
if (command === "likes") {
|
|
@@ -9675,13 +9749,21 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9675
9749
|
return;
|
|
9676
9750
|
}
|
|
9677
9751
|
if (command === "chat") {
|
|
9678
|
-
await runChat(profileOverride, subcommand, parsedArgs.flags);
|
|
9752
|
+
await runChat(profileOverride, subcommand, rest, parsedArgs.flags);
|
|
9679
9753
|
return;
|
|
9680
9754
|
}
|
|
9681
9755
|
if (command === "config") {
|
|
9682
9756
|
await runConfigCommand(subcommand, parsedArgs.flags, profileOverride);
|
|
9683
9757
|
return;
|
|
9684
9758
|
}
|
|
9759
|
+
if (command === "inbox") {
|
|
9760
|
+
await runInbox(profileOverride);
|
|
9761
|
+
return;
|
|
9762
|
+
}
|
|
9763
|
+
if (command === "watch") {
|
|
9764
|
+
await runWatch(profileOverride, parsedArgs.flags);
|
|
9765
|
+
return;
|
|
9766
|
+
}
|
|
9685
9767
|
throw new Error(`Unknown command: ${command}`);
|
|
9686
9768
|
}
|
|
9687
9769
|
async function runHome(profileOverride) {
|
|
@@ -9690,7 +9772,7 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9690
9772
|
profile = await service.syncInbox(profile);
|
|
9691
9773
|
await store.saveProfile(profile);
|
|
9692
9774
|
await store.setActiveProfile(preset.brand, profile.profileName);
|
|
9693
|
-
|
|
9775
|
+
showSection(renderProfileCard(profile), "Current Profile");
|
|
9694
9776
|
const action = await askSelect({
|
|
9695
9777
|
message: "What do you want to do next?",
|
|
9696
9778
|
options: [
|
|
@@ -9753,7 +9835,7 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9753
9835
|
continue;
|
|
9754
9836
|
}
|
|
9755
9837
|
if (action === "profile-show") {
|
|
9756
|
-
|
|
9838
|
+
showSection(renderProfileCard(profile, true), "Profile Details");
|
|
9757
9839
|
continue;
|
|
9758
9840
|
}
|
|
9759
9841
|
if (action === "switch-profile") {
|
|
@@ -9783,6 +9865,23 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9783
9865
|
});
|
|
9784
9866
|
return;
|
|
9785
9867
|
}
|
|
9868
|
+
if (subcommand === "import") {
|
|
9869
|
+
await promptProfileImport({
|
|
9870
|
+
profileName: getStringFlag(flags, "name", "profile-name"),
|
|
9871
|
+
displayName: getStringFlag(flags, "display-name"),
|
|
9872
|
+
ageRange: getStringFlag(flags, "age-range"),
|
|
9873
|
+
region: getStringFlag(flags, "region"),
|
|
9874
|
+
bio: getStringFlag(flags, "bio"),
|
|
9875
|
+
interests: getStringFlag(flags, "interests"),
|
|
9876
|
+
lookingForAge: getStringFlag(flags, "looking-age", "looking-age-range"),
|
|
9877
|
+
lookingForRegions: getStringFlag(flags, "looking-regions"),
|
|
9878
|
+
lookingForNotes: getStringFlag(flags, "looking-notes"),
|
|
9879
|
+
relays: getStringFlag(flags, "relays"),
|
|
9880
|
+
nsec: getStringFlag(flags, "nsec"),
|
|
9881
|
+
publish: Boolean(flags.publish)
|
|
9882
|
+
});
|
|
9883
|
+
return;
|
|
9884
|
+
}
|
|
9786
9885
|
if (subcommand === "use") {
|
|
9787
9886
|
await promptProfileUse(args[0] ?? profileOverride);
|
|
9788
9887
|
return;
|
|
@@ -9791,17 +9890,30 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9791
9890
|
const profiles = await store.listProfiles();
|
|
9792
9891
|
const active = await store.getActiveProfileName(preset.brand);
|
|
9793
9892
|
if (profiles.length === 0) {
|
|
9794
|
-
|
|
9893
|
+
showInfo("No profiles yet. Start with `profile create`.");
|
|
9795
9894
|
return;
|
|
9796
9895
|
}
|
|
9797
|
-
|
|
9896
|
+
showSection(profiles.map((name) => `${name === active ? "●" : "○"} ${name}`).join("\n"), "Profiles");
|
|
9798
9897
|
return;
|
|
9799
9898
|
}
|
|
9800
9899
|
if (subcommand === "show") {
|
|
9801
|
-
|
|
9900
|
+
showSection(renderProfileCard(await ensureProfile(profileOverride), true), "Profile Details");
|
|
9802
9901
|
return;
|
|
9803
9902
|
}
|
|
9804
|
-
|
|
9903
|
+
if (subcommand === "edit") {
|
|
9904
|
+
await promptProfileEdit(await ensureProfile(profileOverride), {
|
|
9905
|
+
displayName: getStringFlag(flags, "display-name"),
|
|
9906
|
+
ageRange: getStringFlag(flags, "age-range"),
|
|
9907
|
+
region: getStringFlag(flags, "region"),
|
|
9908
|
+
bio: getStringFlag(flags, "bio"),
|
|
9909
|
+
interests: getStringFlag(flags, "interests"),
|
|
9910
|
+
lookingForAge: getStringFlag(flags, "looking-age", "looking-age-range"),
|
|
9911
|
+
lookingForRegions: getStringFlag(flags, "looking-regions"),
|
|
9912
|
+
lookingForNotes: getStringFlag(flags, "looking-notes")
|
|
9913
|
+
});
|
|
9914
|
+
return;
|
|
9915
|
+
}
|
|
9916
|
+
throw new Error("Use `profile create|import|edit|use|list|show`.");
|
|
9805
9917
|
}
|
|
9806
9918
|
async function runListingCommand(subcommand, _args, flags, profileOverride) {
|
|
9807
9919
|
const profile = await ensureProfile(profileOverride);
|
|
@@ -9817,14 +9929,14 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9817
9929
|
if (subcommand === "list") {
|
|
9818
9930
|
const refreshed = await service.refreshOwnListings(profile);
|
|
9819
9931
|
await store.saveProfile(refreshed);
|
|
9820
|
-
|
|
9932
|
+
showSection(renderListings(refreshed.cache.listings), "Your Listings");
|
|
9821
9933
|
return;
|
|
9822
9934
|
}
|
|
9823
9935
|
if (subcommand === "close") {
|
|
9824
9936
|
const refreshed = await service.refreshOwnListings(profile);
|
|
9825
9937
|
const openListings = refreshed.cache.listings.filter((listing) => listing.status === "open");
|
|
9826
9938
|
if (openListings.length === 0) {
|
|
9827
|
-
|
|
9939
|
+
showInfo("There are no open listings to close.");
|
|
9828
9940
|
return;
|
|
9829
9941
|
}
|
|
9830
9942
|
const listingIdArg = getStringFlag(flags, "id", "listing-id");
|
|
@@ -9839,15 +9951,50 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9839
9951
|
hint: listing.region
|
|
9840
9952
|
}))
|
|
9841
9953
|
});
|
|
9842
|
-
const nextProfile = await
|
|
9954
|
+
const nextProfile = await runWithSpinner("Closing listing...", () => service.closeListing(refreshed, listingId));
|
|
9843
9955
|
await store.saveProfile(nextProfile);
|
|
9844
|
-
|
|
9956
|
+
showSuccess("Listing closed.");
|
|
9957
|
+
return;
|
|
9958
|
+
}
|
|
9959
|
+
if (subcommand === "edit") {
|
|
9960
|
+
const refreshed = await service.refreshOwnListings(profile);
|
|
9961
|
+
await store.saveProfile(refreshed);
|
|
9962
|
+
await promptListingEdit(refreshed, {
|
|
9963
|
+
listingRef: getStringFlag(flags, "id", "listing-id", "address", "listing-address") ?? _args[0],
|
|
9964
|
+
headline: getStringFlag(flags, "title", "headline"),
|
|
9965
|
+
summary: getStringFlag(flags, "summary"),
|
|
9966
|
+
region: getStringFlag(flags, "region"),
|
|
9967
|
+
desiredTags: getStringFlag(flags, "tags", "desired-tags")
|
|
9968
|
+
});
|
|
9845
9969
|
return;
|
|
9846
9970
|
}
|
|
9847
|
-
|
|
9971
|
+
if (subcommand === "reopen") {
|
|
9972
|
+
const refreshed = await service.refreshOwnListings(profile);
|
|
9973
|
+
await store.saveProfile(refreshed);
|
|
9974
|
+
await reopenListing(refreshed, getStringFlag(flags, "id", "listing-id", "address", "listing-address") ?? _args[0]);
|
|
9975
|
+
return;
|
|
9976
|
+
}
|
|
9977
|
+
throw new Error("Use `listing publish|list|close|edit|reopen`.");
|
|
9848
9978
|
}
|
|
9849
|
-
async function
|
|
9850
|
-
|
|
9979
|
+
async function runDiscoverCommand(subcommand, args, flags, profileOverride) {
|
|
9980
|
+
const profile = await ensureProfile(profileOverride);
|
|
9981
|
+
if (!subcommand) {
|
|
9982
|
+
await handleDiscover(profile);
|
|
9983
|
+
return;
|
|
9984
|
+
}
|
|
9985
|
+
if (subcommand === "list") {
|
|
9986
|
+
await showDiscoverList(profile);
|
|
9987
|
+
return;
|
|
9988
|
+
}
|
|
9989
|
+
if (subcommand === "like") {
|
|
9990
|
+
await likeDiscoveredListing(profile, args[0] ?? getStringFlag(flags, "id", "listing", "address"), flags);
|
|
9991
|
+
return;
|
|
9992
|
+
}
|
|
9993
|
+
if (subcommand === "pass") {
|
|
9994
|
+
await passDiscoveredListing(profile, args[0] ?? getStringFlag(flags, "id", "listing", "address"));
|
|
9995
|
+
return;
|
|
9996
|
+
}
|
|
9997
|
+
throw new Error("Use `discover`, `discover list`, `discover like <listing>`, or `discover pass <listing>`.");
|
|
9851
9998
|
}
|
|
9852
9999
|
async function runLikes(profileOverride) {
|
|
9853
10000
|
await handleLikes(await ensureProfile(profileOverride));
|
|
@@ -9855,23 +10002,46 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9855
10002
|
async function runMatches(profileOverride) {
|
|
9856
10003
|
await handleMatches(await ensureProfile(profileOverride));
|
|
9857
10004
|
}
|
|
9858
|
-
async function runChat(profileOverride,
|
|
9859
|
-
|
|
10005
|
+
async function runChat(profileOverride, chatArg, args = [], flags = {}) {
|
|
10006
|
+
const profile = await ensureProfile(profileOverride);
|
|
10007
|
+
const message = getStringFlag(flags, "message");
|
|
10008
|
+
if (chatArg === "list") {
|
|
10009
|
+
await showConversationList(profile);
|
|
10010
|
+
return;
|
|
10011
|
+
}
|
|
10012
|
+
if (chatArg === "show") {
|
|
10013
|
+
await showConversationHistory(profile, args[0] ?? getStringFlag(flags, "thread-id"));
|
|
10014
|
+
return;
|
|
10015
|
+
}
|
|
10016
|
+
if (chatArg && message === void 0) {
|
|
10017
|
+
await showConversationHistory(profile, chatArg);
|
|
10018
|
+
return;
|
|
10019
|
+
}
|
|
10020
|
+
await handleChat(profile, chatArg, void 0, message);
|
|
9860
10021
|
}
|
|
9861
10022
|
async function runConfigCommand(subcommand, flags, profileOverride) {
|
|
9862
10023
|
const profile = await ensureProfile(profileOverride);
|
|
9863
10024
|
if (subcommand === "show") {
|
|
9864
|
-
|
|
10025
|
+
showSection(renderProfileCard(profile, true), "Advanced Config");
|
|
9865
10026
|
return;
|
|
9866
10027
|
}
|
|
9867
10028
|
if (subcommand === "relays") {
|
|
9868
10029
|
const nextProfile = await promptRelayConfig(profile, getStringFlag(flags, "relays"));
|
|
9869
10030
|
await store.saveProfile(nextProfile);
|
|
9870
|
-
|
|
10031
|
+
showSuccess("Relay list updated.");
|
|
9871
10032
|
return;
|
|
9872
10033
|
}
|
|
9873
10034
|
throw new Error("Use `config show|relays`.");
|
|
9874
10035
|
}
|
|
10036
|
+
async function runInbox(profileOverride) {
|
|
10037
|
+
await showInbox(await ensureProfile(profileOverride));
|
|
10038
|
+
}
|
|
10039
|
+
async function runWatch(profileOverride, flags) {
|
|
10040
|
+
const profile = await ensureProfile(profileOverride);
|
|
10041
|
+
const intervalSeconds = Number.parseInt(getStringFlag(flags, "interval") ?? "10", 10);
|
|
10042
|
+
if (!Number.isFinite(intervalSeconds) || intervalSeconds <= 0) throw new Error("Interval must be a positive number of seconds.");
|
|
10043
|
+
await watchInbox(profile, intervalSeconds);
|
|
10044
|
+
}
|
|
9875
10045
|
async function ensureProfile(profileOverride) {
|
|
9876
10046
|
const explicit = profileOverride ? await store.readProfile(profileOverride) : null;
|
|
9877
10047
|
if (explicit) return explicit;
|
|
@@ -9890,19 +10060,58 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9890
10060
|
return promptProfileCreate();
|
|
9891
10061
|
}
|
|
9892
10062
|
async function promptProfileCreate(initial = {}) {
|
|
9893
|
-
const
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
10063
|
+
const draft = await collectProfileDraft(initial);
|
|
10064
|
+
const credentials = createGeneratedCredentials();
|
|
10065
|
+
return saveProfileAndOptionallyPublish(createDefaultProfile(preset, {
|
|
10066
|
+
profileName: draft.profileName,
|
|
10067
|
+
displayName: draft.displayName,
|
|
10068
|
+
bio: draft.bio,
|
|
10069
|
+
region: draft.region,
|
|
10070
|
+
ageRange: draft.ageRange,
|
|
10071
|
+
interests: splitComma(draft.interests),
|
|
10072
|
+
lookingFor: {
|
|
10073
|
+
ageRange: draft.lookingForAge,
|
|
10074
|
+
regions: splitComma(draft.lookingForRegions),
|
|
10075
|
+
notes: draft.lookingForNotes
|
|
10076
|
+
},
|
|
10077
|
+
nostr: credentials,
|
|
10078
|
+
relays: draft.relays
|
|
10079
|
+
}), "Preparing profile...", "Profile Created", true);
|
|
10080
|
+
}
|
|
10081
|
+
async function promptProfileImport(initial = {}) {
|
|
10082
|
+
const draft = await collectProfileDraft(initial);
|
|
10083
|
+
const credentials = importCredentials(await resolveRequiredInput(initial.nsec, {
|
|
10084
|
+
message: "Enter the nsec to import.",
|
|
10085
|
+
placeholder: "nsec1...",
|
|
9897
10086
|
validate(value) {
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
10087
|
+
try {
|
|
10088
|
+
importCredentials(value ?? "");
|
|
10089
|
+
} catch (error) {
|
|
10090
|
+
return error instanceof Error ? error.message : "Invalid nsec value.";
|
|
10091
|
+
}
|
|
9901
10092
|
}
|
|
9902
|
-
});
|
|
10093
|
+
}));
|
|
10094
|
+
return saveProfileAndOptionallyPublish(createDefaultProfile(preset, {
|
|
10095
|
+
profileName: draft.profileName,
|
|
10096
|
+
displayName: draft.displayName,
|
|
10097
|
+
bio: draft.bio,
|
|
10098
|
+
region: draft.region,
|
|
10099
|
+
ageRange: draft.ageRange,
|
|
10100
|
+
interests: splitComma(draft.interests),
|
|
10101
|
+
lookingFor: {
|
|
10102
|
+
ageRange: draft.lookingForAge,
|
|
10103
|
+
regions: splitComma(draft.lookingForRegions),
|
|
10104
|
+
notes: draft.lookingForNotes
|
|
10105
|
+
},
|
|
10106
|
+
nostr: credentials,
|
|
10107
|
+
relays: draft.relays
|
|
10108
|
+
}), "Importing profile...", initial.publish ? "Profile Imported & Published" : "Profile Imported", Boolean(initial.publish));
|
|
10109
|
+
}
|
|
10110
|
+
async function promptProfileEdit(profile, initial = {}) {
|
|
9903
10111
|
const displayName = await resolveRequiredInput(initial.displayName, {
|
|
9904
10112
|
message: "What display name should we show?",
|
|
9905
10113
|
placeholder: preset.brand === "create-kanojo" ? "たくみ" : "あや",
|
|
10114
|
+
defaultValue: profile.displayName,
|
|
9906
10115
|
validate(value) {
|
|
9907
10116
|
if (!(value?.trim() ?? "")) return "Display name is required.";
|
|
9908
10117
|
}
|
|
@@ -9910,6 +10119,7 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9910
10119
|
const ageRange = await resolveRequiredInput(initial.ageRange, {
|
|
9911
10120
|
message: "How would you describe your age range?",
|
|
9912
10121
|
placeholder: "20代後半",
|
|
10122
|
+
defaultValue: profile.ageRange,
|
|
9913
10123
|
validate(value) {
|
|
9914
10124
|
if (!(value?.trim() ?? "")) return "Age range is required.";
|
|
9915
10125
|
}
|
|
@@ -9917,6 +10127,7 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9917
10127
|
const region = await resolveRequiredInput(initial.region, {
|
|
9918
10128
|
message: "Which area do you usually meet in?",
|
|
9919
10129
|
placeholder: "東京",
|
|
10130
|
+
defaultValue: profile.region,
|
|
9920
10131
|
validate(value) {
|
|
9921
10132
|
if (!(value?.trim() ?? "")) return "Region is required.";
|
|
9922
10133
|
}
|
|
@@ -9924,51 +10135,117 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9924
10135
|
const bio = await resolveRequiredInput(initial.bio, {
|
|
9925
10136
|
message: "Write a short intro.",
|
|
9926
10137
|
placeholder: "映画とコーヒーが好きです。",
|
|
10138
|
+
defaultValue: profile.bio,
|
|
9927
10139
|
validate(value) {
|
|
9928
10140
|
if (!(value?.trim() ?? "")) return "Bio is required.";
|
|
9929
10141
|
}
|
|
9930
10142
|
});
|
|
9931
10143
|
const interests = await resolveOptionalInput(initial.interests, {
|
|
9932
10144
|
message: "List interests or vibe tags, comma separated.",
|
|
10145
|
+
defaultValue: profile.interests.join(", "),
|
|
9933
10146
|
placeholder: "映画, カフェ, 散歩"
|
|
9934
10147
|
});
|
|
9935
10148
|
const lookingForAge = await resolveOptionalInput(initial.lookingForAge, {
|
|
9936
10149
|
message: "What age range are you looking for?",
|
|
10150
|
+
defaultValue: profile.lookingFor.ageRange,
|
|
9937
10151
|
placeholder: "20代"
|
|
9938
10152
|
});
|
|
9939
10153
|
const lookingForRegions = await resolveOptionalInput(initial.lookingForRegions, {
|
|
9940
10154
|
message: "Which regions do you want to meet in? Use commas.",
|
|
10155
|
+
defaultValue: profile.lookingFor.regions.join(", "),
|
|
9941
10156
|
placeholder: region
|
|
9942
10157
|
});
|
|
9943
10158
|
const lookingForNotes = await resolveOptionalInput(initial.lookingForNotes, {
|
|
9944
10159
|
message: "What kind of person feels right for you?",
|
|
10160
|
+
defaultValue: profile.lookingFor.notes,
|
|
9945
10161
|
placeholder: "落ち着いて話せる人"
|
|
9946
10162
|
});
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
const profile = createDefaultProfile(preset, {
|
|
9950
|
-
profileName,
|
|
10163
|
+
return saveProfileAndOptionallyPublish({
|
|
10164
|
+
...profile,
|
|
9951
10165
|
displayName,
|
|
9952
|
-
bio,
|
|
9953
|
-
region,
|
|
9954
10166
|
ageRange,
|
|
10167
|
+
region,
|
|
10168
|
+
bio,
|
|
9955
10169
|
interests: splitComma(interests),
|
|
9956
10170
|
lookingFor: {
|
|
9957
10171
|
ageRange: lookingForAge,
|
|
9958
10172
|
regions: splitComma(lookingForRegions),
|
|
9959
10173
|
notes: lookingForNotes
|
|
9960
|
-
}
|
|
9961
|
-
|
|
9962
|
-
|
|
10174
|
+
}
|
|
10175
|
+
}, "Updating profile...", "Profile Updated", true);
|
|
10176
|
+
}
|
|
10177
|
+
async function collectProfileDraft(initial) {
|
|
10178
|
+
const profileName = await resolveRequiredInput(initial.profileName, {
|
|
10179
|
+
message: "Choose a profile name.",
|
|
10180
|
+
placeholder: "main",
|
|
10181
|
+
defaultValue: "main",
|
|
10182
|
+
validate(value) {
|
|
10183
|
+
const normalized = value?.trim() ?? "";
|
|
10184
|
+
if (!normalized) return "Profile name is required.";
|
|
10185
|
+
if (!/^[a-z0-9-]+$/.test(normalized)) return "Use lowercase letters, digits, and hyphens only.";
|
|
10186
|
+
}
|
|
10187
|
+
});
|
|
10188
|
+
const displayName = await resolveRequiredInput(initial.displayName, {
|
|
10189
|
+
message: "What display name should we show?",
|
|
10190
|
+
placeholder: preset.brand === "create-kanojo" ? "たくみ" : "あや",
|
|
10191
|
+
validate(value) {
|
|
10192
|
+
if (!(value?.trim() ?? "")) return "Display name is required.";
|
|
10193
|
+
}
|
|
10194
|
+
});
|
|
10195
|
+
const ageRange = await resolveRequiredInput(initial.ageRange, {
|
|
10196
|
+
message: "How would you describe your age range?",
|
|
10197
|
+
placeholder: "20代後半",
|
|
10198
|
+
validate(value) {
|
|
10199
|
+
if (!(value?.trim() ?? "")) return "Age range is required.";
|
|
10200
|
+
}
|
|
9963
10201
|
});
|
|
9964
|
-
const
|
|
10202
|
+
const region = await resolveRequiredInput(initial.region, {
|
|
10203
|
+
message: "Which area do you usually meet in?",
|
|
10204
|
+
placeholder: "東京",
|
|
10205
|
+
validate(value) {
|
|
10206
|
+
if (!(value?.trim() ?? "")) return "Region is required.";
|
|
10207
|
+
}
|
|
10208
|
+
});
|
|
10209
|
+
return {
|
|
10210
|
+
profileName,
|
|
10211
|
+
displayName,
|
|
10212
|
+
ageRange,
|
|
10213
|
+
region,
|
|
10214
|
+
bio: await resolveRequiredInput(initial.bio, {
|
|
10215
|
+
message: "Write a short intro.",
|
|
10216
|
+
placeholder: "映画とコーヒーが好きです。",
|
|
10217
|
+
validate(value) {
|
|
10218
|
+
if (!(value?.trim() ?? "")) return "Bio is required.";
|
|
10219
|
+
}
|
|
10220
|
+
}),
|
|
10221
|
+
interests: await resolveOptionalInput(initial.interests, {
|
|
10222
|
+
message: "List interests or vibe tags, comma separated.",
|
|
10223
|
+
placeholder: "映画, カフェ, 散歩"
|
|
10224
|
+
}),
|
|
10225
|
+
lookingForAge: await resolveOptionalInput(initial.lookingForAge, {
|
|
10226
|
+
message: "What age range are you looking for?",
|
|
10227
|
+
placeholder: "20代"
|
|
10228
|
+
}),
|
|
10229
|
+
lookingForRegions: await resolveOptionalInput(initial.lookingForRegions, {
|
|
10230
|
+
message: "Which regions do you want to meet in? Use commas.",
|
|
10231
|
+
placeholder: region
|
|
10232
|
+
}),
|
|
10233
|
+
lookingForNotes: await resolveOptionalInput(initial.lookingForNotes, {
|
|
10234
|
+
message: "What kind of person feels right for you?",
|
|
10235
|
+
placeholder: "落ち着いて話せる人"
|
|
10236
|
+
}),
|
|
10237
|
+
relays: initial.relays ? normalizeRelayList(initial.relays) : DEFAULT_RELAYS
|
|
10238
|
+
};
|
|
10239
|
+
}
|
|
10240
|
+
async function saveProfileAndOptionallyPublish(profile, spinnerMessage, title, publish) {
|
|
10241
|
+
const nextProfile = publish ? await runWithSpinner(spinnerMessage, async () => {
|
|
9965
10242
|
await service.publishProfile(profile);
|
|
9966
10243
|
return profile;
|
|
9967
|
-
});
|
|
9968
|
-
await store.saveProfile(
|
|
9969
|
-
await store.setActiveProfile(preset.brand,
|
|
9970
|
-
|
|
9971
|
-
return
|
|
10244
|
+
}) : profile;
|
|
10245
|
+
await store.saveProfile(nextProfile);
|
|
10246
|
+
await store.setActiveProfile(preset.brand, nextProfile.profileName);
|
|
10247
|
+
showSection(renderProfileCard(nextProfile), title);
|
|
10248
|
+
return nextProfile;
|
|
9972
10249
|
}
|
|
9973
10250
|
async function promptProfileUse(profileNameArg) {
|
|
9974
10251
|
const profiles = await store.listProfiles();
|
|
@@ -9988,7 +10265,7 @@ function createMatchingCli(preset, options = {}) {
|
|
|
9988
10265
|
const profile = await store.readProfile(selectedName);
|
|
9989
10266
|
if (!profile) throw new Error(`Profile not found: ${selectedName}`);
|
|
9990
10267
|
await store.setActiveProfile(preset.brand, selectedName);
|
|
9991
|
-
|
|
10268
|
+
showSection(renderProfileCard(profile), "Active Profile");
|
|
9992
10269
|
return profile;
|
|
9993
10270
|
}
|
|
9994
10271
|
async function handlePublishListing(profile, initial = {}) {
|
|
@@ -10014,44 +10291,90 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10014
10291
|
message: "Enter tags, comma separated.",
|
|
10015
10292
|
placeholder: "映画, 落ち着き, 夜カフェ"
|
|
10016
10293
|
});
|
|
10017
|
-
const nextProfile = await
|
|
10294
|
+
const nextProfile = await runWithSpinner("Publishing listing...", () => service.publishListing(profile, {
|
|
10018
10295
|
headline,
|
|
10019
10296
|
summary,
|
|
10020
10297
|
region,
|
|
10021
10298
|
desiredTags: splitComma(desiredTags)
|
|
10022
10299
|
}));
|
|
10023
10300
|
await store.saveProfile(nextProfile);
|
|
10024
|
-
|
|
10301
|
+
showSuccess("Listing published.");
|
|
10025
10302
|
return nextProfile;
|
|
10026
10303
|
}
|
|
10027
|
-
async function
|
|
10028
|
-
const
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10304
|
+
async function promptListingEdit(profile, initial = {}) {
|
|
10305
|
+
const listing = await resolveOwnListing(profile, initial.listingRef, false);
|
|
10306
|
+
const headline = await resolveRequiredInput(initial.headline, {
|
|
10307
|
+
message: "Enter the listing title.",
|
|
10308
|
+
placeholder: "週末に一緒に映画を見に行ける人",
|
|
10309
|
+
defaultValue: listing.headline,
|
|
10310
|
+
validate(value) {
|
|
10311
|
+
if (!(value?.trim() ?? "")) return "Title is required.";
|
|
10312
|
+
}
|
|
10035
10313
|
});
|
|
10036
|
-
await
|
|
10314
|
+
const summary = await resolveRequiredInput(initial.summary, {
|
|
10315
|
+
message: "Write a short summary.",
|
|
10316
|
+
placeholder: "まずはお茶からゆっくり話したいです。",
|
|
10317
|
+
defaultValue: listing.summary,
|
|
10318
|
+
validate(value) {
|
|
10319
|
+
if (!(value?.trim() ?? "")) return "Summary is required.";
|
|
10320
|
+
}
|
|
10321
|
+
});
|
|
10322
|
+
const region = await resolveOptionalInput(initial.region, {
|
|
10323
|
+
message: "Which region is this listing for?",
|
|
10324
|
+
defaultValue: listing.region
|
|
10325
|
+
});
|
|
10326
|
+
const desiredTags = await resolveOptionalInput(initial.desiredTags, {
|
|
10327
|
+
message: "Enter tags, comma separated.",
|
|
10328
|
+
defaultValue: listing.desiredTags.join(", "),
|
|
10329
|
+
placeholder: "映画, 落ち着き, 夜カフェ"
|
|
10330
|
+
});
|
|
10331
|
+
const nextProfile = await runWithSpinner("Updating listing...", () => service.updateListing(profile, {
|
|
10332
|
+
listingId: listing.id,
|
|
10333
|
+
headline,
|
|
10334
|
+
summary,
|
|
10335
|
+
region,
|
|
10336
|
+
desiredTags: splitComma(desiredTags)
|
|
10337
|
+
}));
|
|
10338
|
+
await store.saveProfile(nextProfile);
|
|
10339
|
+
showSuccess("Listing updated.");
|
|
10340
|
+
return nextProfile;
|
|
10341
|
+
}
|
|
10342
|
+
async function reopenListing(profile, listingRef) {
|
|
10343
|
+
const listing = await resolveOwnListing(profile, listingRef, true);
|
|
10344
|
+
const nextProfile = await runWithSpinner("Reopening listing...", () => service.updateListing(profile, {
|
|
10345
|
+
listingId: listing.id,
|
|
10346
|
+
status: "open"
|
|
10347
|
+
}));
|
|
10348
|
+
await store.saveProfile(nextProfile);
|
|
10349
|
+
showSuccess("Listing reopened.");
|
|
10350
|
+
return nextProfile;
|
|
10351
|
+
}
|
|
10352
|
+
async function resolveOwnListing(profile, listingRef, closedOnly = false) {
|
|
10353
|
+
const listings = profile.cache.listings.filter((listing) => closedOnly ? listing.status === "closed" : true);
|
|
10354
|
+
if (listings.length === 0) throw new Error(closedOnly ? "There are no closed listings to reopen." : "There are no listings yet.");
|
|
10355
|
+
const selected = (listingRef ? listings.find((listing) => listing.id === listingRef || listing.address === listingRef) : null) ?? null;
|
|
10356
|
+
if (listingRef && !selected) throw new Error("Listing not found. Use `listing list` to check the id or address.");
|
|
10357
|
+
if (selected) return selected;
|
|
10358
|
+
const listingId = await askSelect({
|
|
10359
|
+
message: closedOnly ? "Choose a listing to reopen." : "Choose a listing to edit.",
|
|
10360
|
+
options: listings.map((listing) => ({
|
|
10361
|
+
value: listing.id,
|
|
10362
|
+
label: listing.headline,
|
|
10363
|
+
hint: `${listing.region} | ${listing.status}`
|
|
10364
|
+
}))
|
|
10365
|
+
});
|
|
10366
|
+
const listing = listings.find((item) => item.id === listingId);
|
|
10367
|
+
if (!listing) throw new Error("Listing not found.");
|
|
10368
|
+
return listing;
|
|
10369
|
+
}
|
|
10370
|
+
async function handleDiscover(profile) {
|
|
10371
|
+
const refreshed = await loadDiscoverState(profile);
|
|
10037
10372
|
if (refreshed.listings.length === 0) {
|
|
10038
|
-
|
|
10373
|
+
showInfo("No listings found right now. Try again later.");
|
|
10039
10374
|
return refreshed.profile;
|
|
10040
10375
|
}
|
|
10041
|
-
const
|
|
10042
|
-
|
|
10043
|
-
R.warn("Publish at least one open listing first.");
|
|
10044
|
-
return refreshed.profile;
|
|
10045
|
-
}
|
|
10046
|
-
const ownListingAddress = openListings.length === 1 ? openListings[0].address : await askSelect({
|
|
10047
|
-
message: "Which of your listings should send the like?",
|
|
10048
|
-
options: openListings.map((item) => ({
|
|
10049
|
-
value: item.address,
|
|
10050
|
-
label: item.headline,
|
|
10051
|
-
hint: item.region
|
|
10052
|
-
}))
|
|
10053
|
-
});
|
|
10054
|
-
Vt([
|
|
10376
|
+
const ownListingAddress = await resolveOwnOpenListingAddress(refreshed.profile);
|
|
10377
|
+
showSection([
|
|
10055
10378
|
"y: send like",
|
|
10056
10379
|
"n: pass for now",
|
|
10057
10380
|
"q: quit discover",
|
|
@@ -10065,38 +10388,144 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10065
10388
|
while (queue.length > 0) {
|
|
10066
10389
|
const current = queue[0];
|
|
10067
10390
|
if (!current) break;
|
|
10068
|
-
|
|
10391
|
+
showSection(renderDiscoverCard(current, queue.length), "Next Candidate");
|
|
10069
10392
|
const action = await askSwipeAction();
|
|
10070
10393
|
if (action === "quit") break;
|
|
10071
|
-
nextProfile = recordSwipeDecision(nextProfile, current, action);
|
|
10072
10394
|
if (action === "yes") {
|
|
10073
|
-
nextProfile = await
|
|
10074
|
-
fromListing: ownListingAddress,
|
|
10075
|
-
toListing: current.address,
|
|
10076
|
-
fromProfileName: nextProfile.profileName,
|
|
10077
|
-
recipientPubkey: current.authorPubkey,
|
|
10078
|
-
recipientRelays: current.inboxRelays
|
|
10079
|
-
}));
|
|
10395
|
+
nextProfile = await sendLikeToDiscoveredListing(nextProfile, current, ownListingAddress);
|
|
10080
10396
|
likedCount += 1;
|
|
10081
|
-
|
|
10397
|
+
showSuccess(`Sent a like to ${current.profileDisplayName}.`);
|
|
10082
10398
|
} else {
|
|
10399
|
+
nextProfile = recordSwipeDecision(nextProfile, current, action);
|
|
10083
10400
|
skippedCount += 1;
|
|
10084
|
-
|
|
10401
|
+
showStep(`Passed on ${current.profileDisplayName} for now.`);
|
|
10085
10402
|
}
|
|
10086
10403
|
await store.saveProfile(nextProfile);
|
|
10087
10404
|
queue = rankDiscoverListings(nextProfile, refreshed.listings);
|
|
10088
10405
|
}
|
|
10089
|
-
|
|
10406
|
+
showSection([
|
|
10090
10407
|
`Likes: ${likedCount}`,
|
|
10091
10408
|
`Passes: ${skippedCount}`,
|
|
10092
10409
|
`Remaining: ${queue.length}`
|
|
10093
10410
|
].join("\n"), "Discover Summary");
|
|
10094
10411
|
return nextProfile;
|
|
10095
10412
|
}
|
|
10413
|
+
async function showDiscoverList(profile) {
|
|
10414
|
+
const refreshed = await loadDiscoverState(profile);
|
|
10415
|
+
if (refreshed.listings.length === 0) {
|
|
10416
|
+
showInfo("No listings found right now. Try again later.");
|
|
10417
|
+
return;
|
|
10418
|
+
}
|
|
10419
|
+
showSection(renderDiscoverListings(refreshed.listings), "Discover");
|
|
10420
|
+
}
|
|
10421
|
+
async function likeDiscoveredListing(profile, listingRef, flags) {
|
|
10422
|
+
if (!listingRef) throw new Error("Use `discover like <listing-id|address>`.");
|
|
10423
|
+
const refreshed = await loadDiscoverState(profile);
|
|
10424
|
+
const listing = resolveDiscoveredListing(refreshed.listings, listingRef);
|
|
10425
|
+
const ownListingAddress = await resolveOwnOpenListingAddress(refreshed.profile, getStringFlag(flags, "from", "from-listing"));
|
|
10426
|
+
const nextProfile = await sendLikeToDiscoveredListing(refreshed.profile, listing, ownListingAddress);
|
|
10427
|
+
await store.saveProfile(nextProfile);
|
|
10428
|
+
showSection(renderDiscoverCard(listing, refreshed.listings.length), "Liked");
|
|
10429
|
+
showSuccess(`Sent a like to ${listing.profileDisplayName}.`);
|
|
10430
|
+
}
|
|
10431
|
+
async function passDiscoveredListing(profile, listingRef) {
|
|
10432
|
+
if (!listingRef) throw new Error("Use `discover pass <listing-id|address>`.");
|
|
10433
|
+
const refreshed = await loadDiscoverState(profile);
|
|
10434
|
+
const listing = resolveDiscoveredListing(refreshed.listings, listingRef);
|
|
10435
|
+
const nextProfile = recordSwipeDecision(refreshed.profile, listing, "no");
|
|
10436
|
+
await store.saveProfile(nextProfile);
|
|
10437
|
+
showSection(renderDiscoverCard(listing, refreshed.listings.length), "Passed");
|
|
10438
|
+
showSuccess(`Passed on ${listing.profileDisplayName}.`);
|
|
10439
|
+
}
|
|
10440
|
+
async function loadDiscoverState(profile) {
|
|
10441
|
+
const refreshed = await runWithSpinner("Looking for people...", async () => {
|
|
10442
|
+
const synced = await service.syncInbox(profile);
|
|
10443
|
+
const withListings = await service.refreshOwnListings(synced);
|
|
10444
|
+
return {
|
|
10445
|
+
profile: withListings,
|
|
10446
|
+
listings: rankDiscoverListings(withListings, await service.discoverListings(withListings))
|
|
10447
|
+
};
|
|
10448
|
+
});
|
|
10449
|
+
await store.saveProfile(refreshed.profile);
|
|
10450
|
+
return refreshed;
|
|
10451
|
+
}
|
|
10452
|
+
async function resolveOwnOpenListingAddress(profile, listingRef) {
|
|
10453
|
+
const openListings = profile.cache.listings.filter((item) => item.status === "open");
|
|
10454
|
+
if (openListings.length === 0) throw new Error("Publish at least one open listing first.");
|
|
10455
|
+
const selected = (listingRef ? openListings.find((item) => item.id === listingRef || item.address === listingRef) : null) ?? null;
|
|
10456
|
+
if (listingRef && !selected) throw new Error("Own listing not found. Use `listing list` to check the id or address.");
|
|
10457
|
+
if (selected) return selected.address;
|
|
10458
|
+
if (openListings.length === 1) return openListings[0].address;
|
|
10459
|
+
return await askSelect({
|
|
10460
|
+
message: "Which of your listings should send the like?",
|
|
10461
|
+
options: openListings.map((item) => ({
|
|
10462
|
+
value: item.address,
|
|
10463
|
+
label: item.headline,
|
|
10464
|
+
hint: item.region
|
|
10465
|
+
}))
|
|
10466
|
+
});
|
|
10467
|
+
}
|
|
10468
|
+
function resolveDiscoveredListing(listings, listingRef) {
|
|
10469
|
+
const listing = listings.find((item) => item.id === listingRef || item.address === listingRef);
|
|
10470
|
+
if (!listing) throw new Error("Discover target not found. Use `discover list` to inspect available listings.");
|
|
10471
|
+
return listing;
|
|
10472
|
+
}
|
|
10473
|
+
async function sendLikeToDiscoveredListing(profile, listing, ownListingAddress) {
|
|
10474
|
+
const withDecision = recordSwipeDecision(profile, listing, "yes");
|
|
10475
|
+
return runWithSpinner("Sending like...", () => service.sendLike(withDecision, {
|
|
10476
|
+
fromListing: ownListingAddress,
|
|
10477
|
+
toListing: listing.address,
|
|
10478
|
+
fromProfileName: withDecision.profileName,
|
|
10479
|
+
recipientPubkey: listing.authorPubkey,
|
|
10480
|
+
recipientRelays: listing.inboxRelays
|
|
10481
|
+
}));
|
|
10482
|
+
}
|
|
10483
|
+
async function showInbox(profile) {
|
|
10484
|
+
const nextProfile = await syncInboxState(profile);
|
|
10485
|
+
showSection(renderInboxSummary(nextProfile), "Inbox");
|
|
10486
|
+
const conversations = buildConversations(nextProfile);
|
|
10487
|
+
if (conversations.length > 0) showSection(renderConversationList(conversations.slice(0, 5)), "Recent Conversations");
|
|
10488
|
+
return nextProfile;
|
|
10489
|
+
}
|
|
10490
|
+
async function watchInbox(profile, intervalSeconds) {
|
|
10491
|
+
let current = await showInbox(profile);
|
|
10492
|
+
showInfo(`Watching inbox every ${intervalSeconds}s. Press Ctrl+C to stop.`);
|
|
10493
|
+
let stopped = false;
|
|
10494
|
+
const onSigint = () => {
|
|
10495
|
+
stopped = true;
|
|
10496
|
+
};
|
|
10497
|
+
process.once("SIGINT", onSigint);
|
|
10498
|
+
try {
|
|
10499
|
+
while (!stopped) {
|
|
10500
|
+
await sleep(intervalSeconds * 1e3);
|
|
10501
|
+
if (stopped) break;
|
|
10502
|
+
const nextProfile = await syncInboxState(current);
|
|
10503
|
+
if (!hasInboxChanged(current, nextProfile)) continue;
|
|
10504
|
+
current = nextProfile;
|
|
10505
|
+
showSection(renderInboxSummary(current), "Inbox Update");
|
|
10506
|
+
const conversations = buildConversations(current);
|
|
10507
|
+
if (conversations.length > 0) showSection(renderConversationList(conversations.slice(0, 3)), "Recent Conversations");
|
|
10508
|
+
}
|
|
10509
|
+
} finally {
|
|
10510
|
+
process.off("SIGINT", onSigint);
|
|
10511
|
+
showInfo("Stopped watching inbox.");
|
|
10512
|
+
}
|
|
10513
|
+
}
|
|
10514
|
+
async function syncInboxState(profile) {
|
|
10515
|
+
const nextProfile = await runWithSpinner("Syncing inbox...", () => service.syncInbox(profile));
|
|
10516
|
+
await store.saveProfile(nextProfile);
|
|
10517
|
+
return nextProfile;
|
|
10518
|
+
}
|
|
10519
|
+
function hasInboxChanged(previous, next) {
|
|
10520
|
+
if (previous.cache.likesReceived.length !== next.cache.likesReceived.length) return true;
|
|
10521
|
+
if (previous.cache.matches.length !== next.cache.matches.length) return true;
|
|
10522
|
+
if (previous.cache.chatMessages.length !== next.cache.chatMessages.length) return true;
|
|
10523
|
+
return previous.cache.lastInboxSyncAt !== next.cache.lastInboxSyncAt;
|
|
10524
|
+
}
|
|
10096
10525
|
async function handleLikes(profile) {
|
|
10097
|
-
const nextProfile = await
|
|
10526
|
+
const nextProfile = await runWithSpinner("Syncing likes...", () => service.syncInbox(profile));
|
|
10098
10527
|
await store.saveProfile(nextProfile);
|
|
10099
|
-
|
|
10528
|
+
showSection(renderLikes(nextProfile), "Likes");
|
|
10100
10529
|
const conversations = getLikedYouConversations(nextProfile);
|
|
10101
10530
|
if (conversations.length === 0) return nextProfile;
|
|
10102
10531
|
if (!await askConfirm({
|
|
@@ -10106,13 +10535,13 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10106
10535
|
return handleChat(nextProfile, void 0, conversations);
|
|
10107
10536
|
}
|
|
10108
10537
|
async function handleMatches(profile) {
|
|
10109
|
-
const nextProfile = await
|
|
10538
|
+
const nextProfile = await runWithSpinner("Syncing matches...", () => service.syncInbox(profile));
|
|
10110
10539
|
await store.saveProfile(nextProfile);
|
|
10111
10540
|
if (nextProfile.cache.matches.length === 0) {
|
|
10112
|
-
|
|
10541
|
+
showInfo("No matches yet. Mutual likes will appear here.");
|
|
10113
10542
|
return nextProfile;
|
|
10114
10543
|
}
|
|
10115
|
-
|
|
10544
|
+
showSection(renderMatches(nextProfile.cache.matches), "Matches");
|
|
10116
10545
|
if (await askConfirm({
|
|
10117
10546
|
message: "Open one now?",
|
|
10118
10547
|
initialValue: true
|
|
@@ -10122,12 +10551,12 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10122
10551
|
async function handleChat(profile, threadIdArg, availableConversations, initialMessage) {
|
|
10123
10552
|
let nextProfile = profile;
|
|
10124
10553
|
if (!availableConversations) {
|
|
10125
|
-
nextProfile = await
|
|
10554
|
+
nextProfile = await runWithSpinner("Syncing conversation...", () => service.syncInbox(profile));
|
|
10126
10555
|
await store.saveProfile(nextProfile);
|
|
10127
10556
|
}
|
|
10128
10557
|
const conversations = availableConversations ?? buildConversations(nextProfile);
|
|
10129
10558
|
if (conversations.length === 0) {
|
|
10130
|
-
|
|
10559
|
+
showInfo("There are no conversations yet.");
|
|
10131
10560
|
return nextProfile;
|
|
10132
10561
|
}
|
|
10133
10562
|
const conversation = (threadIdArg ? conversations.find((item) => item.threadId === threadIdArg) : null) ?? await promptConversation(conversations);
|
|
@@ -10137,7 +10566,7 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10137
10566
|
if (!body) throw new Error("Message is required.");
|
|
10138
10567
|
return sendChatMessage(nextProfile, conversation, body);
|
|
10139
10568
|
}
|
|
10140
|
-
|
|
10569
|
+
showSection(renderConversation(conversation), `chat | ${conversation.peerProfileName}`);
|
|
10141
10570
|
while (true) {
|
|
10142
10571
|
const body = await askText({
|
|
10143
10572
|
message: "Enter a message. Leave it blank to exit.",
|
|
@@ -10152,8 +10581,27 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10152
10581
|
})) return nextProfile;
|
|
10153
10582
|
}
|
|
10154
10583
|
}
|
|
10584
|
+
async function showConversationList(profile) {
|
|
10585
|
+
const conversations = buildConversations(await syncConversations(profile));
|
|
10586
|
+
if (conversations.length === 0) {
|
|
10587
|
+
showInfo("There are no conversations yet.");
|
|
10588
|
+
return;
|
|
10589
|
+
}
|
|
10590
|
+
showSection(renderConversationList(conversations), "Conversations");
|
|
10591
|
+
}
|
|
10592
|
+
async function showConversationHistory(profile, threadIdArg) {
|
|
10593
|
+
if (!threadIdArg) throw new Error("Use `chat show <thread-id>` or `chat <thread-id>`.");
|
|
10594
|
+
const conversation = buildConversations(await syncConversations(profile)).find((item) => item.threadId === threadIdArg);
|
|
10595
|
+
if (!conversation) throw new Error("Conversation not found.");
|
|
10596
|
+
showSection(renderConversation(conversation), `chat | ${conversation.peerProfileName}`);
|
|
10597
|
+
}
|
|
10598
|
+
async function syncConversations(profile) {
|
|
10599
|
+
const nextProfile = await runWithSpinner("Syncing conversation...", () => service.syncInbox(profile));
|
|
10600
|
+
await store.saveProfile(nextProfile);
|
|
10601
|
+
return nextProfile;
|
|
10602
|
+
}
|
|
10155
10603
|
async function sendChatMessage(profile, conversation, body) {
|
|
10156
|
-
const nextProfile = await
|
|
10604
|
+
const nextProfile = await runWithSpinner("Sending message...", () => service.sendChat(profile, {
|
|
10157
10605
|
matchId: conversation.threadId,
|
|
10158
10606
|
recipientPubkey: conversation.peerPubkey,
|
|
10159
10607
|
recipientRelays: conversation.peerRelays,
|
|
@@ -10161,8 +10609,8 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10161
10609
|
}));
|
|
10162
10610
|
await store.saveProfile(nextProfile);
|
|
10163
10611
|
const refreshedConversation = buildConversations(nextProfile).find((item) => item.threadId === conversation.threadId);
|
|
10164
|
-
if (refreshedConversation)
|
|
10165
|
-
|
|
10612
|
+
if (refreshedConversation) showSection(renderConversation(refreshedConversation), `chat | ${refreshedConversation.peerProfileName}`);
|
|
10613
|
+
showSuccess("Message sent.");
|
|
10166
10614
|
return nextProfile;
|
|
10167
10615
|
}
|
|
10168
10616
|
async function promptConfig(profile) {
|
|
@@ -10186,14 +10634,14 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10186
10634
|
]
|
|
10187
10635
|
});
|
|
10188
10636
|
if (action === "show") {
|
|
10189
|
-
|
|
10637
|
+
showSection(renderProfileCard(profile, true), "Advanced Config");
|
|
10190
10638
|
return profile;
|
|
10191
10639
|
}
|
|
10192
10640
|
if (action === "relays") return promptRelayConfig(profile);
|
|
10193
10641
|
return profile;
|
|
10194
10642
|
}
|
|
10195
10643
|
async function promptRelayConfig(profile, relayInputArg) {
|
|
10196
|
-
|
|
10644
|
+
showSection(profile.relays.join("\n"), "Current Relays");
|
|
10197
10645
|
const relayInput = relayInputArg ?? await askText({
|
|
10198
10646
|
message: "Enter new relays, comma separated.",
|
|
10199
10647
|
defaultValue: profile.relays.join(", "),
|
|
@@ -10205,7 +10653,7 @@ function createMatchingCli(preset, options = {}) {
|
|
|
10205
10653
|
}
|
|
10206
10654
|
}
|
|
10207
10655
|
});
|
|
10208
|
-
const nextProfile = await
|
|
10656
|
+
const nextProfile = await runWithSpinner("Updating relays...", () => service.updateRelays(profile, normalizeRelayList(relayInput)));
|
|
10209
10657
|
await store.saveProfile(nextProfile);
|
|
10210
10658
|
return nextProfile;
|
|
10211
10659
|
}
|
|
@@ -10230,9 +10678,21 @@ function renderCliUsage(preset) {
|
|
|
10230
10678
|
" profile list",
|
|
10231
10679
|
" profile use <name>",
|
|
10232
10680
|
" profile show",
|
|
10681
|
+
" profile edit --display-name \"<name>\" --bio \"...\"",
|
|
10682
|
+
" profile import --name main --nsec nsec1... --publish",
|
|
10233
10683
|
" profile create --name main --display-name \"<name>\" --age-range \"20代後半\" --region 東京 --bio \"映画とコーヒーが好きです。\" --interests \"映画, カフェ\" --looking-age \"20代\" --looking-regions \"東京, 神奈川\" --looking-notes \"落ち着いて話せる人\"",
|
|
10234
10684
|
" listing publish --title \"週末に一緒に映画を見に行ける人\" --summary \"まずはお茶からゆっくり話したいです。\" --region 東京 --tags \"映画, 夜カフェ\"",
|
|
10685
|
+
" listing edit <listing-id> --title \"更新タイトル\"",
|
|
10235
10686
|
" listing close --id <listing-id>",
|
|
10687
|
+
" listing reopen <listing-id>",
|
|
10688
|
+
" discover list",
|
|
10689
|
+
" discover like <listing-id> --from <your-listing-id>",
|
|
10690
|
+
" discover pass <listing-id>",
|
|
10691
|
+
" inbox",
|
|
10692
|
+
" watch --interval 10",
|
|
10693
|
+
" chat list",
|
|
10694
|
+
" chat show <thread-id>",
|
|
10695
|
+
" chat <thread-id>",
|
|
10236
10696
|
" chat <thread-id> --message \"こんにちは\"",
|
|
10237
10697
|
" config relays --relays \"wss://relay1.example,wss://relay2.example\"",
|
|
10238
10698
|
"",
|
|
@@ -10317,7 +10777,8 @@ async function askConfirm(options) {
|
|
|
10317
10777
|
if (Ct$1(answer)) throw new CancelledFlowError();
|
|
10318
10778
|
return Boolean(answer);
|
|
10319
10779
|
}
|
|
10320
|
-
async function withSpinner(message, task) {
|
|
10780
|
+
async function withSpinner(message, task, plain = false) {
|
|
10781
|
+
if (plain) return task();
|
|
10321
10782
|
const indicator = be();
|
|
10322
10783
|
indicator.start(message);
|
|
10323
10784
|
try {
|
|
@@ -10366,10 +10827,12 @@ function renderProfileCard(profile, advanced = false) {
|
|
|
10366
10827
|
}
|
|
10367
10828
|
function renderListings(listings) {
|
|
10368
10829
|
if (listings.length === 0) return "No listings yet.";
|
|
10369
|
-
return listings.map((listing) => `${formatListingChoiceLabel(listing)}\n ${listing.summary}\n ${listing.address}`).join("\n\n");
|
|
10830
|
+
return listings.map((listing) => `${formatListingChoiceLabel(listing)}\n id: ${listing.id}\n updated: ${formatTimestamp(listing.updatedAt)}\n ${listing.summary}\n ${listing.address}`).join("\n\n");
|
|
10370
10831
|
}
|
|
10371
10832
|
function renderDiscoverCard(listing, remainingCount) {
|
|
10372
10833
|
return [
|
|
10834
|
+
`id: ${listing.id}`,
|
|
10835
|
+
`address: ${listing.address}`,
|
|
10373
10836
|
`score: ${listing.score}`,
|
|
10374
10837
|
`Remaining: ${remainingCount}`,
|
|
10375
10838
|
`${listing.profileDisplayName} | ${listing.region}`,
|
|
@@ -10381,13 +10844,16 @@ function renderDiscoverCard(listing, remainingCount) {
|
|
|
10381
10844
|
`Why: ${listing.reasons.join(" / ") || "Exploring new patterns"}`
|
|
10382
10845
|
].join("\n");
|
|
10383
10846
|
}
|
|
10847
|
+
function renderDiscoverListings(listings) {
|
|
10848
|
+
return listings.map((listing, index) => `${index + 1}. ${listing.profileDisplayName} | ${listing.headline}\n id: ${listing.id}\n address: ${listing.address}\n region: ${listing.region}\n score: ${listing.score}\n tags: ${listing.desiredTags.join(", ") || "None"}\n why: ${listing.reasons.join(" / ") || "Exploring new patterns"}`).join("\n\n");
|
|
10849
|
+
}
|
|
10384
10850
|
function renderLikes(profile) {
|
|
10385
|
-
const sent = profile.cache.likesSent.map((like) => `→ ${like.toListing}\n ${like.fromProfileName} / ${(
|
|
10851
|
+
const sent = profile.cache.likesSent.map((like) => `→ ${like.toListing}\n ${like.fromProfileName} / ${formatTimestamp(like.createdAt)}`).join("\n\n");
|
|
10386
10852
|
const likedYouMap = new Map(getLikedYouConversations(profile).map((conversation) => [conversation.threadId, conversation]));
|
|
10387
10853
|
const received = profile.cache.likesReceived.map((like) => {
|
|
10388
10854
|
const conversation = likedYouMap.get(like.matchId);
|
|
10389
10855
|
const dmState = conversation ? `DM ready (${conversation.messages.length} msgs)` : "DM ready";
|
|
10390
|
-
return `← ${like.fromProfileName}\n ${like.fromListing}\n ${(
|
|
10856
|
+
return `← ${like.fromProfileName}\n ${like.fromListing}\n ${formatTimestamp(like.createdAt)}\n ${dmState}`;
|
|
10391
10857
|
}).join("\n\n");
|
|
10392
10858
|
return [
|
|
10393
10859
|
"Sent Likes",
|
|
@@ -10398,20 +10864,39 @@ function renderLikes(profile) {
|
|
|
10398
10864
|
].join("\n");
|
|
10399
10865
|
}
|
|
10400
10866
|
function renderMatches(matches) {
|
|
10401
|
-
return matches.map((match) => `${match.peerProfileName}\n ${match.matchId}\n Updated: ${(
|
|
10867
|
+
return matches.map((match) => `${match.peerProfileName}\n ${match.matchId}\n Updated: ${formatTimestamp(match.updatedAt)}`).join("\n\n");
|
|
10402
10868
|
}
|
|
10403
10869
|
function renderConversation(conversation) {
|
|
10404
10870
|
const messages = conversation.messages.map((message) => {
|
|
10405
|
-
|
|
10871
|
+
const speaker = message.senderPubkey === conversation.peerPubkey ? conversation.peerProfileName : "You";
|
|
10872
|
+
return `[${formatTimestamp(message.createdAt)}] ${speaker} (${message.rumorId}): ${message.body}`;
|
|
10406
10873
|
}).join("\n");
|
|
10407
10874
|
return [
|
|
10408
10875
|
`Conversation with ${conversation.peerProfileName}`,
|
|
10409
10876
|
`Thread: ${conversation.threadId}`,
|
|
10410
10877
|
`Source: ${conversation.source}`,
|
|
10878
|
+
`Messages: ${conversation.messages.length}`,
|
|
10879
|
+
`Updated: ${formatTimestamp(conversation.updatedAt)}`,
|
|
10411
10880
|
"",
|
|
10412
10881
|
messages || "No messages yet."
|
|
10413
10882
|
].join("\n");
|
|
10414
10883
|
}
|
|
10884
|
+
function renderConversationList(conversations) {
|
|
10885
|
+
return conversations.map((conversation) => `${conversation.peerProfileName}\n ${conversation.threadId}\n Source: ${conversation.source}\n Messages: ${conversation.messages.length}\n Updated: ${formatTimestamp(conversation.updatedAt)}`).join("\n\n");
|
|
10886
|
+
}
|
|
10887
|
+
function renderInboxSummary(profile) {
|
|
10888
|
+
const latestConversation = buildConversations(profile)[0];
|
|
10889
|
+
return [
|
|
10890
|
+
`Last Sync: ${profile.cache.lastInboxSyncAt ? formatTimestamp(profile.cache.lastInboxSyncAt) : "Never"}`,
|
|
10891
|
+
`Likes Received: ${profile.cache.likesReceived.length}`,
|
|
10892
|
+
`Matches: ${profile.cache.matches.length}`,
|
|
10893
|
+
`Messages: ${profile.cache.chatMessages.length}`,
|
|
10894
|
+
`Latest Thread: ${latestConversation ? `${latestConversation.peerProfileName} (${formatTimestamp(latestConversation.updatedAt)})` : "None"}`
|
|
10895
|
+
].join("\n");
|
|
10896
|
+
}
|
|
10897
|
+
function formatTimestamp(unixSeconds) {
|
|
10898
|
+
return (/* @__PURE__ */ new Date(unixSeconds * 1e3)).toLocaleString("ja-JP");
|
|
10899
|
+
}
|
|
10415
10900
|
function splitComma(value) {
|
|
10416
10901
|
return [...new Set(value.split(",").map((item) => item.trim()).filter(Boolean))];
|
|
10417
10902
|
}
|
|
@@ -10447,6 +10932,9 @@ function normalizeRelayList(value) {
|
|
|
10447
10932
|
if (!items.every((item) => item.startsWith("wss://"))) throw new Error("Relay URLs must start with wss://.");
|
|
10448
10933
|
return items;
|
|
10449
10934
|
}
|
|
10935
|
+
function sleep(ms) {
|
|
10936
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10937
|
+
}
|
|
10450
10938
|
async function askSwipeAction() {
|
|
10451
10939
|
if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
10452
10940
|
const normalized = normalizeSwipeAction(await askText({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kanojo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A Nostr-based dating CLI for finding a kanojo.",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"vite-plus": "^0.1.11"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"typescript": "^5"
|
|
31
|
-
"@repo/shared": "@repo/shared"
|
|
30
|
+
"typescript": "^5"
|
|
32
31
|
}
|
|
33
32
|
}
|