@deslop/stash 0.1.1082 → 0.1.1085

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/README.md CHANGED
@@ -19,7 +19,7 @@ The CLI saves the entry through the server's RPC, waits until the server has rea
19
19
  - **Media.** The server runs the `yt-dlp` on `PATH` and downloads the Whisper model into `$XDG_CACHE_HOME/deslop/media` on first use. Published captions win; Whisper `small.en` takes about a quarter of the audio's length, up to two hours of audio. TikTok, X, YouTube and Instagram links go through `yt-dlp`, also when their page could not be read.
20
20
  - **Proxy.** When `STASH_PROXY` holds an `http://` proxy URL, every request for a source exits through it: `yt-dlp`, pages, APIs, preview images and the reader. Without it, the server reads with its own address. `{session}` in the URL becomes a new random value for each `yt-dlp` run and each request, so a rotating residential proxy keeps one exit address while a run downloads. A request that a site blocks, or that the exit drops, retries twice from new exit addresses. On `dev` it is the owner's [DataImpulse](https://app.dataimpulse.com) residential plan "stash": pay-as-you-go traffic that never expires, with `__sessid.{session}` after the login.
21
21
  - **Sources.** Web pages become Markdown with their headings, lists and link targets. GitHub repositories give their README, issues and pull requests their comments, X posts their long-form articles, and TikTok photo posts every slide. A page that holds no text, such as a JavaScript app or a PDF, goes to the [Jina Reader](https://jina.ai/reader/), which also takes blocked pages; its bot-check pages count as failures.
22
- - **Storage.** SQLite at `stash.db` in `STASH_DATA_DIR`, one row per note, without migrations: a change to its shape can drop it. Previews and uploaded files sit beside it in `images/` and `files/`.
22
+ - **Storage.** SQLite at `stash.db` in `STASH_DATA_DIR`, one row per note holding its JSON. The notes on `dev` are production data: a change to the note's shape ships with a migration that converts the stored rows, never a reset. Previews and uploaded files sit beside it in `images/` and `files/`.
23
23
  - **Sync.** `notes.watch` sends every note's list fields once, then only the notes that changed, at most every 400 ms. `notes.get` loads the long texts and `notes.search` searches transcripts and pages on the server.
24
24
  - **Routes.** The app uses the JSON WebSocket RPC at `/api/rpc` and loads previews from `/api/images/<id>`. The share extension posts `{id, text}` to `/api/capture`. Photos and videos arrive as the raw body of `/api/files/<id>`.
25
25
  - **Organizer.** Claude reads each entry as a developer would and writes the notes the owner reads instead of the source: a plain title of at most 32 characters, a one-sentence summary, one to three tags, search topics, short Markdown notes of at most 10 lines (facts, steps, bold names and links, with headings only for long sources), and the words in the images. The preview, the photo post's slides and 12 evenly spaced video stills, up to 768 pixels wide, go with it. A short link resolves before saving, so another share of the same post finds the saved note.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env -S node --max-old-space-size=16384 --heapsnapshot-near-heap-limit=3 --report-on-fatalerror
2
- import { $a as catchCauseIf, $d as isNotUndefined, $l as findLast, $o as orDie, $u as getOrUndefinedUnsafe, Al as sync, Ao as interrupt$1, Bl as taggedEnum, Bo as map$2, Bu as succeed$2, Co as forkIn, Cs as tapCause, Ct as Defect, D as option, Do as gen, Ds as timeoutOrElse, Dt as FalseLiterals, Ec as findError, Fc as squash, Fo as logError, Fs as useSpan, Ft as NonEmptyArray, Gc as provide$1, Gd as format, Gs as exponential, Ho as mapError, Hs as withParentSpan, I as layerJson, Id as withFiber$1, Is as void_, J as isStreamSchema, Ji as bounded, Jo as onError, Ju as Service, Ka as annotateLogs, Kl as empty$2, Kn as empty, Ks as min, Ku as toMillis, Ld as assignProperty, Lo as logInfo, Md as Prototype, Mo as isEffect, Ms as uninterruptibleMask, N as withRunClient, Nu as fail$2, Oo as ignore, P as RpcSerialization, Q as makeCollectorUnsafe, Qa as catchCause, Qc as addFinalizerExit, Ql as findFirstIndex, R as RequestId, Rc as effect, Ri as unwrap, Rs as whileLoop, Ru as isSuccess, Sd as none, Sl as ConsoleRef, Sn as tag, Td as some, Ts as timeout, Tt as Error$1, Uo as match$2, Ut as TaggedError, V as constPing, Vt as String$6, W as exitSchema, Wa as andThen, Wo as matchCauseEffect, Wt as TrueLiterals, X as spanName, Xa as cached, Xc as Scope, Xi as end, Xn as merge, Xu as empty$1, Y as spanAttributes, Yd as hasProperty, Yl as filter$1, Yu as add, Z as Collector, Za as callback, Zc as addFinalizer, Zi as failCause, Zl as findFirst, Zo as onInterrupt, Zt as Union, Zu as get, _ as QuitError, _c as Clock, _d as isSome, _l as interrupt, _s as serviceOption, _t as ArraySchema, ao as catchTags, bd as map$3, bn as optional$2, bo as forEach, bs as suspend, bu as some$1, c as NotesError, ca as makeUnsafe, ci as fromQueue, cs as result, da as interrupt$2, df as identity, do as ensuring, du as join, ea as offerAll, ec as isEmpty, ed as getUnsafe, ei as filter, en as decodeSync, et as Socket, fd as fromUndefinedOr, ff as pipe, fs as runForkWith, ft as HttpClientErrorSchema, g as layer, gc as round, gd as isNone, gl as failCause$1, go as flatMap$1, h as errorReported, hd as getOrUndefined, hl as fail, hs as scoped, hu as match, ia as take, in as encodeEffect, io as catchTag, is as provideService, it as SocketReadError, j as Stdio, jc as interruptors, jo as interruptible, jt as Literal, kc as hasInterrupts, ki as runHead, ko as ignoreCause, lf as dual, ln as is, lo as delay, m as errorExitCode, ml as die, mu as map$4, nn as decodeUnknownEffect, nr as value, ns as provide, nt as SocketErrorReason, oc as replace, od as exists, os as raceFirst, ot as layerWebSocket, p as runMain, pd as getOrElse, pf as pipeArguments, po as fail$1, q as getStreamSchemas, qn as fromInput, qs as spaced, qu as Reference, ra as shutdownUnsafe, rs as provideContext, sf as constVoid, sl as MinimumLogLevel, so as context, st as layerWebSocketConstructorGlobal, t as RpcContracts, tc as isNonEmpty, td as make$3, to as catchFilter, tr as make$2, tt as SocketError, u as LogToStderr, ua as getCurrent, us as retryOrElse, uu as isReadonlyArrayNonEmpty, v as Terminal, vs as succeed$1, vu as reduce, w as String$7, wn as toCodecStringTree, wo as forkScoped, x as Crypto, xd as match$1, xn as optionalKey, xo as forever, xs as sync$1, y as isQuitError, yl as isFailure, yo as fnUntraced, yt as BooleanLiterals, zo as makeSpanScoped, zs as withFiber } from "./contracts-Cc_B2S3-.mjs";
2
+ import { $o as match$2, $t as TaggedError, Ad as match$1, An as optional$2, Ao as forEach, As as suspend, Au as some$1, Bl as sync, Bo as interrupt$1, C as runMain, Co as fail$1, D as QuitError, Dc as Clock, Dl as interrupt, Ds as serviceOption, Dt as ArraySchema, E as layer, Ec as round, Ed as isSome, El as failCause$1, Eo as flatMap$1, Fs as timeout, Ft as Error$1, Gc as squash, Gd as withFiber$1, Go as logError, Gs as useSpan, Gt as NonEmptyArray, Ho as isEffect, Hs as uninterruptibleMask, Hu as fail$2, Ic as findError, J as RequestId, Jc as effect, Ji as unwrap, Js as whileLoop, K as layerJson, Kd as assignProperty, Ks as void_, L as option, Lo as gen, Ls as timeoutOrElse, Lt as FalseLiterals, Ml as ConsoleRef, Mn as tag, No as forkIn, Ns as tapCause, Nt as Defect, O as Terminal, Os as succeed$1, Ou as reduce, P as String$7, Pd as some, Pn as toCodecStringTree, Po as forkScoped, Qo as mapError, Qs as withParentSpan, Ro as ignore, Sd as getOrElse, Sf as pipeArguments, Ss as runForkWith, St as HttpClientErrorSchema, T as errorReported, Td as isNone, Tl as fail, Ts as scoped, Tu as match, U as withRunClient, V as Stdio, Vc as interruptors, Vd as Prototype, Vo as interruptible, Vt as Literal, W as RpcSerialization, Xl as taggedEnum, Xo as map$2, Yo as makeSpanScoped, Ys as withFiber, Yu as succeed$2, Z as constPing, Zt as String$6, _l as MinimumLogLevel, _o as context, _t as layerWebSocketConstructorGlobal, ad as empty$1, at as spanAttributes, au as filter$2, b as LogToStderr, ba as getCurrent, bf as identity, bs as retryOrElse, bu as isReadonlyArrayNonEmpty, cd as getOrUndefinedUnsafe, cf as isNotUndefined, cl as addFinalizerExit, co as catchCause, ct as makeCollectorUnsafe, cu as findFirstIndex, dc as isNonEmpty, do as catchFilter, dr as make$2, dt as SocketError, ef as format, en as TrueLiterals, eo as andThen, es as matchCauseEffect, et as exitSchema, fn as decodeUnknownEffect, fr as value, fs as provide$1, ft as SocketErrorReason, gc as replace, gd as filter, gf as constVoid, gs as raceFirst, gt as layerWebSocket, h as NotesError, hd as exists, ho as catchTags, ia as bounded, id as add, if as hasProperty, is as onError, it as isStreamSchema, j as Crypto, jd as none, jn as optionalKey, jo as forever, js as sync$1, k as isQuitError, kd as map$3, kl as isFailure, ko as fnUntraced, kt as BooleanLiterals, ld as getUnsafe, lo as catchCauseIf, ls as orDie, lu as findLast, ma as take, mn as encodeEffect, mo as catchTag, ms as provideService, mt as SocketReadError, nc as min, nd as Reference, no as annotateLogs, nr as empty, nu as empty$2, oa as end, od as get, ol as Scope, oo as cached, or as merge, ot as spanName, pa as shutdownUnsafe, ps as provideContext, pu as getSomes, qo as logInfo, qu as isSuccess, rc as spaced, rd as Service, rr as fromInput, rt as getStreamSchemas, sa as failCause, sl as addFinalizer, sn as Union, so as callback, ss as onInterrupt, st as Collector, su as findFirst, t as RpcContracts, tc as exponential, td as toMillis, tl as provide$2, ua as offerAll, ud as make$3, ui as filter$1, un as decodeSync, ut as Socket, va as makeUnsafe, vf as dual, vi as fromQueue, vs as result, w as errorExitCode, wd as getOrUndefined, wl as die, wu as map$4, xa as interrupt$2, xd as fromUndefinedOr, xf as pipe, xo as ensuring, xu as join, yn as is, yo as delay, zc as hasInterrupts, zi as runHead, zo as ignoreCause } from "./contracts-B0mJTvsC.mjs";
3
3
  //#region ../../node_modules/.pnpm/effect@4.0.5/node_modules/effect/dist/workers/WorkerError.js
4
4
  /**
5
5
  * Worker error reason for failures while spawning or setting up a worker.
@@ -7285,6 +7285,76 @@ const withExamples = /*#__PURE__*/ dual(2, (self, examples) => makeCommand({
7285
7285
  ...toImpl(self),
7286
7286
  examples
7287
7287
  }));
7288
+ const mapHandler = (self, f) => {
7289
+ const impl = toImpl(self);
7290
+ return makeCommand({
7291
+ ...impl,
7292
+ handle: (input, path) => f(impl.handle(input, path), input)
7293
+ });
7294
+ };
7295
+ /**
7296
+ * Provides the handler of a command with the services produced by a layer
7297
+ * that optionally depends on the command-line input to be created.
7298
+ *
7299
+ * **Example** (Providing command services)
7300
+ *
7301
+ * ```ts import.meta.vitest
7302
+ * import { Effect, FileSystem, Layer, Path, PlatformError, Stdio, Terminal } from "effect"
7303
+ * import { Command, Flag } from "effect/cli"
7304
+ * import { ChildProcessSpawner } from "effect/process"
7305
+ *
7306
+ * const CliTestLayer = Layer.mergeAll(
7307
+ * FileSystem.layerNoop({}),
7308
+ * Path.layer,
7309
+ * Stdio.layerTest({}),
7310
+ * Layer.succeed(Terminal.Terminal, Terminal.make({
7311
+ * columns: Effect.succeed(80),
7312
+ * rows: Effect.succeed(24),
7313
+ * readInput: Effect.die("unused"),
7314
+ * readLine: Effect.die("unused"),
7315
+ * display: () => Effect.void
7316
+ * })),
7317
+ * Layer.succeed(
7318
+ * ChildProcessSpawner.ChildProcessSpawner,
7319
+ * ChildProcessSpawner.make(() => Effect.die("unused"))
7320
+ * )
7321
+ * )
7322
+ *
7323
+ * const output: Array<string> = []
7324
+ * const deploy = Command.make("deploy", {
7325
+ * env: Flag.String("env")
7326
+ * }, (config) =>
7327
+ * Effect.gen(function*() {
7328
+ * const fs = yield* FileSystem.FileSystem
7329
+ * yield* Effect.sync(() => output.push(`Using file system for ${config.env}`))
7330
+ * })).pipe(
7331
+ * // Provide FileSystem based on the --env flag
7332
+ * Command.provide((config) =>
7333
+ * config.env === "local"
7334
+ * ? FileSystem.layerNoop({})
7335
+ * : FileSystem.layerNoop({
7336
+ * access: () =>
7337
+ * Effect.fail(
7338
+ * PlatformError.badArgument({
7339
+ * module: "FileSystem",
7340
+ * method: "access"
7341
+ * })
7342
+ * )
7343
+ * })
7344
+ * )
7345
+ * )
7346
+ *
7347
+ * await Effect.runPromise(
7348
+ * Command.runWith(deploy, { version: "1.0.0" })(["--env", "local"]).pipe(Effect.provide(CliTestLayer))
7349
+ * )
7350
+ * output // => ["Using file system for local"]
7351
+ * ```
7352
+ *
7353
+ * @stability unstable
7354
+ * @category providing services
7355
+ * @since 4.0.0
7356
+ */
7357
+ const provide = /*#__PURE__*/ dual((args) => isCommand(args[0]), (self, layer, options) => mapHandler(self, (handler, input) => provide$1(handler, typeof layer === "function" ? layer(input) : layer, options)));
7288
7358
  const getOutOfScopeGlobalFlagErrors = (allFlags, activeFlags, flagMap, commandPath) => {
7289
7359
  const activeSet = new Set(activeFlags);
7290
7360
  const errors = [];
@@ -7542,12 +7612,11 @@ const runWith = (command, config) => {
7542
7612
  };
7543
7613
  //#endregion
7544
7614
  //#region package.json
7545
- var version = "0.1.1082";
7615
+ var version = "0.1.1085";
7546
7616
  //#endregion
7547
7617
  //#region src/main.cli.ts
7548
7618
  function section(title, text) {
7549
- if (isEmpty(text)) return "";
7550
- return `## ${title}\n\n${text}`;
7619
+ return pipe(fromUndefinedOr(text), filter(isNonEmpty), map$3((found) => `## ${title}\n\n${found}`));
7551
7620
  }
7552
7621
  const save = pipe(make("save", {
7553
7622
  entry: pipe(String$2("text"), withDescription$2("A link, or the text of a note")),
@@ -7560,32 +7629,32 @@ const save = pipe(make("save", {
7560
7629
  text: input.entry
7561
7630
  });
7562
7631
  yield* logInfo("Saved; the server is reading and tagging it, which takes minutes for a long video");
7563
- yield* pipe(client["notes.watch"](void 0), filter((event) => some$1(event.upserted, (item) => item.id === saved.id && item.status !== "organizing")), runHead, timeoutOrElse({
7632
+ yield* pipe(client["notes.watch"](void 0), filter$1((event) => some$1(event.upserted, (item) => item.id === saved.id && item.status !== "organizing")), runHead, timeoutOrElse({
7564
7633
  duration: "2 hours",
7565
7634
  orElse: () => fail$1(NotesError.make({ message: "The server did not finish the note within two hours." }))
7566
7635
  }));
7567
7636
  return yield* client["notes.get"]({ id: saved.id });
7568
- }), scoped, provide(pipe(layerProtocolSocket({ pingTimeout: "15 seconds" }), provide$1(layerWebSocket(pipe(new URL("/api/rpc", input.server).href, replace(/^http/u, "ws")))), provide$1(layerWebSocketConstructorGlobal), provide$1(layerJson))));
7637
+ }), scoped);
7569
7638
  yield* log(pipe([
7570
- `# ${note.title}`,
7571
- join(filter$1([
7639
+ some(`# ${note.title}`),
7640
+ some(join(filter$2([
7572
7641
  note.author,
7573
7642
  note.source,
7574
- note.url ?? ""
7575
- ], isNonEmpty), " · "),
7576
- join(map$4(note.tags, (name) => `#${name}`), " "),
7577
- note.summary,
7578
- section("Notes", note.body ?? ""),
7579
- section("Topics", note.topics ?? ""),
7580
- section("Text in the images", note.screenText ?? ""),
7643
+ note.url
7644
+ ], isNotUndefined), " · ")),
7645
+ some(join(map$4(note.tags, (name) => `#${name}`), " ")),
7646
+ some(note.summary),
7647
+ section("Notes", note.body),
7648
+ section("Topics", note.topics),
7649
+ section("Text in the images", note.screenText),
7581
7650
  section("Source", note.content),
7582
- section("Transcript", note.transcript ?? ""),
7651
+ section("Transcript", note.transcript),
7583
7652
  section("Issue", note.issue)
7584
- ], filter$1(isNonEmpty), join("\n\n")));
7585
- })), withDescription("Save a link or a note to stash and print it as Markdown: title, summary, tags, notes, source text and the transcript with [mm:ss] timestamps. The server reads the link, transcribes its video with published captions or Whisper, and tags it; the CLI waits until it finishes. Needs the tailnet."), withExamples([{
7653
+ ], getSomes, filter$2(isNonEmpty), join("\n\n")));
7654
+ })), provide((input) => pipe(layerProtocolSocket({ pingTimeout: "15 seconds" }), provide$2(layerWebSocket(pipe(new URL("/api/rpc", input.server).href, replace(/^http/u, "ws")))), provide$2(layerWebSocketConstructorGlobal), provide$2(layerJson))), withDescription("Save a link or a note to stash and print it as Markdown: title, summary, tags, notes, source text and the transcript with [mm:ss] timestamps. The server reads the link, transcribes its video with published captions or Whisper, and tags it; the CLI waits until it finishes. Needs the tailnet."), withExamples([{
7586
7655
  command: "vpx @deslop/stash@latest save https://x.com/poteto/status/2102050467505430555",
7587
7656
  description: "Save the talk attached to an X post and print its note and transcript."
7588
7657
  }]));
7589
- runMain(pipe(make("stash"), withDescription("Save links and notes to stash; the server and iOS app share this package."), withSubcommands([save]), run({ version }), provideService(LogToStderr, true), provide(layer)));
7658
+ runMain(pipe(make("stash"), withDescription("Save links and notes to stash; the server and iOS app share this package."), withSubcommands([save]), run({ version }), provideService(LogToStderr, true), provide$1(layer)));
7590
7659
  //#endregion
7591
7660
  export {};