@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 +1 -1
- package/dist/cli.js +88 -19
- package/dist/{contracts-Cc_B2S3-.mjs → contracts-B0mJTvsC.mjs} +150 -71
- package/dist/server.js +204 -290
- package/package.json +2 -1
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
|
|
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 { $
|
|
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.
|
|
7615
|
+
var version = "0.1.1085";
|
|
7546
7616
|
//#endregion
|
|
7547
7617
|
//#region src/main.cli.ts
|
|
7548
7618
|
function section(title, text) {
|
|
7549
|
-
|
|
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
|
|
7637
|
+
}), scoped);
|
|
7569
7638
|
yield* log(pipe([
|
|
7570
|
-
`# ${note.title}
|
|
7571
|
-
join(filter$
|
|
7639
|
+
some(`# ${note.title}`),
|
|
7640
|
+
some(join(filter$2([
|
|
7572
7641
|
note.author,
|
|
7573
7642
|
note.source,
|
|
7574
|
-
note.url
|
|
7575
|
-
],
|
|
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$
|
|
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 {};
|