fluncle 0.111.0 → 0.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/fluncle.mjs +204 -7
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -557,7 +557,7 @@ function parseVersion(version) {
557
557
  var currentVersion;
558
558
  var init_version = __esm(() => {
559
559
  init_output();
560
- currentVersion = "0.111.0".trim() ? "0.111.0".trim() : "0.1.0";
560
+ currentVersion = "0.113.0".trim() ? "0.113.0".trim() : "0.1.0";
561
561
  });
562
562
 
563
563
  // src/update-notifier.ts
@@ -2209,7 +2209,7 @@ function parseVersion2(version) {
2209
2209
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2210
2210
  var init_version2 = __esm(() => {
2211
2211
  init_output();
2212
- currentVersion2 = "0.111.0".trim() ? "0.111.0".trim() : "0.1.0";
2212
+ currentVersion2 = "0.113.0".trim() ? "0.113.0".trim() : "0.1.0";
2213
2213
  });
2214
2214
 
2215
2215
  // ../../packages/registry/src/index.ts
@@ -2244,6 +2244,18 @@ var init_src = __esm(() => {
2244
2244
  url: `${SITE}/log`,
2245
2245
  weights: { ssh: "secondary", web: "primary" }
2246
2246
  },
2247
+ {
2248
+ exposedContent: [
2249
+ "Fluncle's Logbook — the voyage as a first-person travelogue, one entry per sector-day",
2250
+ "/logbook/:sector — one day written up, the findings inlined as photos"
2251
+ ],
2252
+ kind: "web_route",
2253
+ name: "web.logbook",
2254
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2255
+ route: "/logbook",
2256
+ url: `${SITE}/logbook`,
2257
+ weights: { ssh: "tertiary", web: "primary" }
2258
+ },
2247
2259
  {
2248
2260
  exposedContent: ["Fluncle's own DJ mixtapes — each a checkpoint set with an F-marked Log ID"],
2249
2261
  kind: "web_route",
@@ -2851,6 +2863,22 @@ var init_src = __esm(() => {
2851
2863
  probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-note", kind: "cron" },
2852
2864
  weights: { status: "hidden" }
2853
2865
  },
2866
+ {
2867
+ command: "fluncle admin logbook gaps",
2868
+ exposedContent: [
2869
+ "author the previous day's Logbook travelogue entry, fill-empty-only (hybrid: one claude -p call)"
2870
+ ],
2871
+ kind: "cron",
2872
+ name: "cron.logbook",
2873
+ operatorNotes: "00:40 Amsterdam daily. Hybrid --no-agent; one claude -p writes the day up. Never clobbers an operator entry; the self-healing gap window backfills history oldest-first. Source: docs/agents/hermes/scripts/logbook-sweep.{sh,ts}.",
2874
+ probeConfig: {
2875
+ cadenceMs: 24 * 60 * MINUTE_MS,
2876
+ cronName: "fluncle-logbook",
2877
+ kind: "cron",
2878
+ schedule: { time: "00:40", tz: "Europe/Amsterdam" }
2879
+ },
2880
+ weights: { status: "hidden" }
2881
+ },
2854
2882
  {
2855
2883
  command: "fluncle admin tracks observe --queue",
2856
2884
  exposedContent: [
@@ -3335,6 +3363,46 @@ var init_track = __esm(() => {
3335
3363
  NON_FILE_OPTIONS = ["model", "reasoning"];
3336
3364
  });
3337
3365
 
3366
+ // src/commands/admin-attention.ts
3367
+ var exports_admin_attention = {};
3368
+ __export(exports_admin_attention, {
3369
+ attentionQueueLines: () => attentionQueueLines,
3370
+ attentionQueueCommand: () => attentionQueueCommand
3371
+ });
3372
+ async function attentionQueueCommand() {
3373
+ const response = await adminApiGet("/api/admin/attention");
3374
+ return response.attention;
3375
+ }
3376
+ function attentionQueueLines(queue) {
3377
+ const lines = [queue.brief];
3378
+ if (queue.rows.length > 0) {
3379
+ const width = queue.rows.reduce((max, row) => Math.max(max, SOURCE_LABELS[row.source].length), 0);
3380
+ lines.push("");
3381
+ for (const row of queue.rows) {
3382
+ lines.push(` ${SOURCE_LABELS[row.source].padEnd(width)} ${row.title}`);
3383
+ }
3384
+ }
3385
+ if (queue.renderQueueDepth > 0) {
3386
+ const noun = queue.renderQueueDepth === 1 ? "finding" : "findings";
3387
+ lines.push("", `${queue.renderQueueDepth} ${noun} in the render queue.`);
3388
+ }
3389
+ return lines;
3390
+ }
3391
+ var SOURCE_LABELS;
3392
+ var init_admin_attention = __esm(() => {
3393
+ init_api();
3394
+ SOURCE_LABELS = {
3395
+ "artist-review": "artist links",
3396
+ "attach-cues": "cues",
3397
+ distribute: "distribute",
3398
+ "drip-empty": "clip drip",
3399
+ "post-tiktok": "tiktok",
3400
+ "post-youtube": "youtube",
3401
+ submission: "submission",
3402
+ "tiktok-draft": "tiktok draft"
3403
+ };
3404
+ });
3405
+
3338
3406
  // src/commands/add.ts
3339
3407
  var exports_add = {};
3340
3408
  __export(exports_add, {
@@ -4781,6 +4849,54 @@ var init_newsletter = __esm(() => {
4781
4849
  init_output();
4782
4850
  });
4783
4851
 
4852
+ // src/commands/admin-logbook.ts
4853
+ var exports_admin_logbook = {};
4854
+ __export(exports_admin_logbook, {
4855
+ logbookUpdateCommand: () => logbookUpdateCommand,
4856
+ logbookGapsCommand: () => logbookGapsCommand,
4857
+ logbookCreateCommand: () => logbookCreateCommand
4858
+ });
4859
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
4860
+ async function logbookGapsCommand(limit) {
4861
+ const query = typeof limit === "number" ? `?limit=${encodeURIComponent(String(limit))}` : "";
4862
+ const response = await adminApiGet(`/api/admin/logbook/gaps${query}`);
4863
+ return response.gaps;
4864
+ }
4865
+ function resolveBody(options) {
4866
+ if (options.body !== undefined) {
4867
+ return options.body;
4868
+ }
4869
+ if (options.bodyFile !== undefined) {
4870
+ if (!existsSync5(options.bodyFile)) {
4871
+ throw new CliError2("file_not_found", `Body file not found: ${options.bodyFile}`);
4872
+ }
4873
+ return readFileSync5(options.bodyFile, "utf-8");
4874
+ }
4875
+ throw new CliError2("missing_body", "An entry needs a body via --body <text> or --body-file <entry.md>");
4876
+ }
4877
+ function requireTitle(options) {
4878
+ if (options.title === undefined || !options.title.trim()) {
4879
+ throw new CliError2("missing_title", "An entry needs a title via --title <text>");
4880
+ }
4881
+ return options.title;
4882
+ }
4883
+ async function logbookCreateCommand(sector, options) {
4884
+ return adminApiPost(`/api/admin/logbook/${encodeURIComponent(sector)}`, {
4885
+ body: resolveBody(options),
4886
+ title: requireTitle(options)
4887
+ });
4888
+ }
4889
+ async function logbookUpdateCommand(sector, options) {
4890
+ return adminApiPatch(`/api/admin/logbook/${encodeURIComponent(sector)}`, {
4891
+ body: resolveBody(options),
4892
+ title: requireTitle(options)
4893
+ });
4894
+ }
4895
+ var init_admin_logbook = __esm(() => {
4896
+ init_api();
4897
+ init_output();
4898
+ });
4899
+
4784
4900
  // src/commands/add.ts
4785
4901
  async function addCommand2(spotifyUrl, options) {
4786
4902
  const result = await adminApiPost("/api/admin/tracks", {
@@ -4806,6 +4922,7 @@ var init_add2 = __esm(() => {
4806
4922
  // src/commands/submissions.ts
4807
4923
  var exports_submissions = {};
4808
4924
  __export(exports_submissions, {
4925
+ triageSubmissionCommand: () => triageSubmissionCommand,
4809
4926
  reviewSubmissionCommand: () => reviewSubmissionCommand,
4810
4927
  rejectSubmissionCommand: () => rejectSubmissionCommand,
4811
4928
  listSubmissionsCommand: () => listSubmissionsCommand,
@@ -4867,6 +4984,14 @@ async function approveSubmissionCommand(submissionId, options = {}) {
4867
4984
  }
4868
4985
  console.log(`Approved ${formatTrackLine(submission)}.`);
4869
4986
  }
4987
+ async function triageSubmissionCommand(submissionId, verdict, options = {}) {
4988
+ const response = await adminApiPost(`/api/admin/submissions/${encodeURIComponent(submissionId)}/triage`, { verdict });
4989
+ if (options.json) {
4990
+ printJson2({ ok: true, submission: response.submission });
4991
+ return;
4992
+ }
4993
+ console.log(`Triaged ${formatTrackLine(response.submission)}: ${response.submission.triageVerdict ?? ""}`);
4994
+ }
4870
4995
  async function fetchSubmission(submissionId) {
4871
4996
  const response = await adminApiGet(`/api/admin/submissions/${encodeURIComponent(submissionId)}`);
4872
4997
  return response.submission;
@@ -5436,7 +5561,7 @@ var init_format2 = __esm(() => {
5436
5561
  });
5437
5562
 
5438
5563
  // src/cli.ts
5439
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
5564
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
5440
5565
  import path2 from "path";
5441
5566
 
5442
5567
  // ../../node_modules/commander/lib/error.js
@@ -7692,6 +7817,10 @@ function addAdminCommands(program2) {
7692
7817
  admin.command("help", { hidden: true }).description("display help for command").action(() => {
7693
7818
  admin.outputHelp();
7694
7819
  });
7820
+ admin.command("queue").description("The attention queue: what's waiting, and the day's dispatch").option("--json", "Print JSON", false).action(async (options) => {
7821
+ const { attentionQueueCommand: attentionQueueCommand2 } = await Promise.resolve().then(() => (init_admin_attention(), exports_admin_attention));
7822
+ await runAdminAttention(options, attentionQueueCommand2);
7823
+ });
7695
7824
  const adminTracks = configureCommand(admin.command("tracks").description("Track admin commands"));
7696
7825
  adminTracks.action(() => {
7697
7826
  adminTracks.outputHelp();
@@ -7916,6 +8045,22 @@ function addAdminCommands(program2) {
7916
8045
  const { newsletterDeleteCommand: newsletterDeleteCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
7917
8046
  await runNewsletterDelete(id, options, newsletterDeleteCommand2);
7918
8047
  });
8048
+ const adminLogbook = configureCommand(admin.command("logbook").description("Fluncle's Logbook (travelogue) commands"));
8049
+ adminLogbook.action(() => {
8050
+ adminLogbook.outputHelp();
8051
+ });
8052
+ adminLogbook.command("gaps").description("List sector-days with findings but no logbook entry (the sweep's worklist)").option("--limit <limit>", "Max gaps to return").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
8053
+ const { logbookGapsCommand: logbookGapsCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8054
+ await runLogbookGaps(options, logbookGapsCommand2);
8055
+ });
8056
+ adminLogbook.command("create").description("Author a sector-day's entry (fills an empty sector only)").argument("[sector]").option("--title <text>", "The entry title").option("--body <text>", "The entry body (markdown; [[logId]] figure tokens)").option("--body-file <file>", "Read the body from a file (the sweep's path)").option("--json", "Print JSON", false).allowExcessArguments().action(async (sector, options) => {
8057
+ const { logbookCreateCommand: logbookCreateCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8058
+ await runLogbookWrite(sector, options, logbookCreateCommand2);
8059
+ });
8060
+ adminLogbook.command("update").description("Create or overwrite a sector-day's entry. OPERATOR only").argument("[sector]").option("--title <text>", "The entry title").option("--body <text>", "The entry body (markdown; [[logId]] figure tokens)").option("--body-file <file>", "Read the body from a file").option("--json", "Print JSON", false).allowExcessArguments().action(async (sector, options) => {
8061
+ const { logbookUpdateCommand: logbookUpdateCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8062
+ await runLogbookWrite(sector, options, logbookUpdateCommand2);
8063
+ });
7919
8064
  const submissions = configureCommand(admin.command("submissions").description("Review listener submissions"));
7920
8065
  submissions.option("--json", "Print JSON", false).action(async (options) => {
7921
8066
  const { listSubmissionsCommand: listSubmissionsCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
@@ -7942,6 +8087,17 @@ function addAdminCommands(program2) {
7942
8087
  const { approveSubmissionCommand: approveSubmissionCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
7943
8088
  await approveSubmissionCommand2(submissionId, options);
7944
8089
  });
8090
+ submissions.command("triage").description("Write the pre-chew triage verdict onto a pending submission").argument("[submissionId]").option("--verdict <text>", "The triage verdict one-liner").option("--verdict-file <file>", "Read the verdict from a file").option("--json", "Print JSON", false).action(async (submissionId, options) => {
8091
+ if (!submissionId) {
8092
+ throw new Error("Missing submission id for: triage");
8093
+ }
8094
+ const verdict = options.verdictFile ? readFileSync6(options.verdictFile, "utf8") : options.verdict;
8095
+ if (!verdict || !verdict.trim()) {
8096
+ throw new Error("Usage: fluncle admin submissions triage <submissionId> (--verdict <text> | --verdict-file <file>) [--json]");
8097
+ }
8098
+ const { triageSubmissionCommand: triageSubmissionCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
8099
+ await triageSubmissionCommand2(submissionId, verdict, { json: options.json });
8100
+ });
7945
8101
  const auth = configureCommand(admin.command("auth").description("Authentication commands"));
7946
8102
  auth.command("spotify").description("Authorize Spotify access").action(async () => {
7947
8103
  const { authSpotifyCommand: authSpotifyCommand2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
@@ -8020,7 +8176,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
8020
8176
  console.log(` mime: ${result.mime}`);
8021
8177
  }
8022
8178
  async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
8023
- const script = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
8179
+ const script = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
8024
8180
  if (!idOrLogId || !script || !script.trim()) {
8025
8181
  throw new Error("Usage: fluncle admin tracks observe <track_id|log_id> (--script <text> | --script-file <file>) [--voice-id <id>] [--duration-ms <ms>] [--context-note <text>] [--json]");
8026
8182
  }
@@ -8076,7 +8232,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
8076
8232
  }
8077
8233
  }
8078
8234
  async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
8079
- const note = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
8235
+ const note = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
8080
8236
  if (!idOrLogId || !note || !note.trim()) {
8081
8237
  throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
8082
8238
  }
@@ -8389,7 +8545,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
8389
8545
  return;
8390
8546
  }
8391
8547
  const candidate = path2.join(dir, name);
8392
- return existsSync5(candidate) ? candidate : undefined;
8548
+ return existsSync6(candidate) ? candidate : undefined;
8393
8549
  };
8394
8550
  const resolveFile = (explicit, name) => {
8395
8551
  if (explicit) {
@@ -8584,7 +8740,7 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand3) {
8584
8740
  if (options.analyzedFrom !== undefined && options.analyzedFrom !== "full" && options.analyzedFrom !== "preview") {
8585
8741
  throw new Error(`Invalid --analyzed-from: ${options.analyzedFrom} (expected full or preview)`);
8586
8742
  }
8587
- const embeddingRaw = options.embeddingFile ? readFileSync5(options.embeddingFile, "utf8") : options.embedding;
8743
+ const embeddingRaw = options.embeddingFile ? readFileSync6(options.embeddingFile, "utf8") : options.embedding;
8588
8744
  const embedding = parseEmbeddingArg(embeddingRaw);
8589
8745
  const result = await trackUpdateCommand3(trackId, {
8590
8746
  analyzedAt: options.analyzedAt,
@@ -8847,6 +9003,37 @@ async function runNewsletterList(options, newsletterListCommand2) {
8847
9003
  console.log(`${label} ${edition.status} ${edition.id} ${subject}`);
8848
9004
  }
8849
9005
  }
9006
+ async function runLogbookGaps(options, logbookGapsCommand2) {
9007
+ const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
9008
+ const gaps = await logbookGapsCommand2(limit !== undefined && Number.isFinite(limit) ? limit : undefined);
9009
+ if (options.json) {
9010
+ printJson({ gaps, ok: true });
9011
+ return;
9012
+ }
9013
+ if (gaps.length === 0) {
9014
+ console.log("No gaps \u2014 every past sector-day with findings has an entry.");
9015
+ return;
9016
+ }
9017
+ for (const gap of gaps) {
9018
+ console.log(`sector ${gap.sector} ${gap.date.slice(0, 10)} ${gap.findings.length} findings`);
9019
+ }
9020
+ }
9021
+ async function runLogbookWrite(sector, options, writeCommand) {
9022
+ if (!sector) {
9023
+ throw new Error("Missing sector for the logbook entry");
9024
+ }
9025
+ const result = await writeCommand(sector, {
9026
+ body: options.body,
9027
+ bodyFile: options.bodyFile,
9028
+ title: options.title
9029
+ });
9030
+ if (options.json) {
9031
+ printJson(result);
9032
+ return;
9033
+ }
9034
+ const skipped = "skipped" in result && result.skipped;
9035
+ console.log(skipped ? `sector ${result.entry.sector}: an entry already stands \u2014 no-op (${result.entry.generatedBy})` : `sector ${result.entry.sector}: ${result.entry.title}`);
9036
+ }
8850
9037
  async function runAdd(spotifyUrl, options, addCommand3) {
8851
9038
  if (!spotifyUrl) {
8852
9039
  throw new Error("Missing Spotify track URL");
@@ -8980,6 +9167,16 @@ async function runAdminTracksList(options, listCommand2) {
8980
9167
  console.log(trackRows2(tracks).join(`
8981
9168
  `));
8982
9169
  }
9170
+ async function runAdminAttention(options, attentionQueueCommand2) {
9171
+ const queue = await attentionQueueCommand2();
9172
+ if (options.json) {
9173
+ printJson({ attention: queue, ok: true });
9174
+ return;
9175
+ }
9176
+ const { attentionQueueLines: attentionQueueLines2 } = await Promise.resolve().then(() => (init_admin_attention(), exports_admin_attention));
9177
+ console.log(attentionQueueLines2(queue).join(`
9178
+ `));
9179
+ }
8983
9180
  async function runAdminQueue(options, queueCommand2) {
8984
9181
  const limit = parseListLimit(options.limit);
8985
9182
  const tracks = await queueCommand2(limit, {
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.111.0"
34
+ "version": "0.113.0"
35
35
  }