fluncle 0.112.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 +131 -8
  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.112.0".trim() ? "0.112.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.112.0".trim() ? "0.112.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: [
@@ -4821,6 +4849,54 @@ var init_newsletter = __esm(() => {
4821
4849
  init_output();
4822
4850
  });
4823
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
+
4824
4900
  // src/commands/add.ts
4825
4901
  async function addCommand2(spotifyUrl, options) {
4826
4902
  const result = await adminApiPost("/api/admin/tracks", {
@@ -5485,7 +5561,7 @@ var init_format2 = __esm(() => {
5485
5561
  });
5486
5562
 
5487
5563
  // src/cli.ts
5488
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
5564
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
5489
5565
  import path2 from "path";
5490
5566
 
5491
5567
  // ../../node_modules/commander/lib/error.js
@@ -7969,6 +8045,22 @@ function addAdminCommands(program2) {
7969
8045
  const { newsletterDeleteCommand: newsletterDeleteCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
7970
8046
  await runNewsletterDelete(id, options, newsletterDeleteCommand2);
7971
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
+ });
7972
8064
  const submissions = configureCommand(admin.command("submissions").description("Review listener submissions"));
7973
8065
  submissions.option("--json", "Print JSON", false).action(async (options) => {
7974
8066
  const { listSubmissionsCommand: listSubmissionsCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
@@ -7999,7 +8091,7 @@ function addAdminCommands(program2) {
7999
8091
  if (!submissionId) {
8000
8092
  throw new Error("Missing submission id for: triage");
8001
8093
  }
8002
- const verdict = options.verdictFile ? readFileSync5(options.verdictFile, "utf8") : options.verdict;
8094
+ const verdict = options.verdictFile ? readFileSync6(options.verdictFile, "utf8") : options.verdict;
8003
8095
  if (!verdict || !verdict.trim()) {
8004
8096
  throw new Error("Usage: fluncle admin submissions triage <submissionId> (--verdict <text> | --verdict-file <file>) [--json]");
8005
8097
  }
@@ -8084,7 +8176,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
8084
8176
  console.log(` mime: ${result.mime}`);
8085
8177
  }
8086
8178
  async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
8087
- const script = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
8179
+ const script = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
8088
8180
  if (!idOrLogId || !script || !script.trim()) {
8089
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]");
8090
8182
  }
@@ -8140,7 +8232,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
8140
8232
  }
8141
8233
  }
8142
8234
  async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
8143
- const note = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
8235
+ const note = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
8144
8236
  if (!idOrLogId || !note || !note.trim()) {
8145
8237
  throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
8146
8238
  }
@@ -8453,7 +8545,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
8453
8545
  return;
8454
8546
  }
8455
8547
  const candidate = path2.join(dir, name);
8456
- return existsSync5(candidate) ? candidate : undefined;
8548
+ return existsSync6(candidate) ? candidate : undefined;
8457
8549
  };
8458
8550
  const resolveFile = (explicit, name) => {
8459
8551
  if (explicit) {
@@ -8648,7 +8740,7 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand3) {
8648
8740
  if (options.analyzedFrom !== undefined && options.analyzedFrom !== "full" && options.analyzedFrom !== "preview") {
8649
8741
  throw new Error(`Invalid --analyzed-from: ${options.analyzedFrom} (expected full or preview)`);
8650
8742
  }
8651
- const embeddingRaw = options.embeddingFile ? readFileSync5(options.embeddingFile, "utf8") : options.embedding;
8743
+ const embeddingRaw = options.embeddingFile ? readFileSync6(options.embeddingFile, "utf8") : options.embedding;
8652
8744
  const embedding = parseEmbeddingArg(embeddingRaw);
8653
8745
  const result = await trackUpdateCommand3(trackId, {
8654
8746
  analyzedAt: options.analyzedAt,
@@ -8911,6 +9003,37 @@ async function runNewsletterList(options, newsletterListCommand2) {
8911
9003
  console.log(`${label} ${edition.status} ${edition.id} ${subject}`);
8912
9004
  }
8913
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
+ }
8914
9037
  async function runAdd(spotifyUrl, options, addCommand3) {
8915
9038
  if (!spotifyUrl) {
8916
9039
  throw new Error("Missing Spotify track URL");
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.112.0"
34
+ "version": "0.113.0"
35
35
  }