fluncle 0.134.0 → 0.136.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 +451 -19
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -561,7 +561,7 @@ function parseVersion(version) {
561
561
  var currentVersion;
562
562
  var init_version = __esm(() => {
563
563
  init_output();
564
- currentVersion = "0.134.0".trim() ? "0.134.0".trim() : "0.1.0";
564
+ currentVersion = "0.136.0".trim() ? "0.136.0".trim() : "0.1.0";
565
565
  });
566
566
 
567
567
  // src/update-notifier.ts
@@ -2189,7 +2189,7 @@ function parseVersion2(version) {
2189
2189
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2190
2190
  var init_version2 = __esm(() => {
2191
2191
  init_output();
2192
- currentVersion2 = "0.134.0".trim() ? "0.134.0".trim() : "0.1.0";
2192
+ currentVersion2 = "0.136.0".trim() ? "0.136.0".trim() : "0.1.0";
2193
2193
  });
2194
2194
 
2195
2195
  // ../../packages/registry/src/index.ts
@@ -3443,6 +3443,9 @@ async function trackObserveCommand(idOrLogId, options) {
3443
3443
  if (options.durationTargetSec !== undefined) {
3444
3444
  body.durationTargetSec = options.durationTargetSec;
3445
3445
  }
3446
+ if (typeof options.promptVersion === "number") {
3447
+ body.promptVersion = options.promptVersion;
3448
+ }
3446
3449
  if (options.contextNote !== undefined) {
3447
3450
  body.contextNote = options.contextNote;
3448
3451
  }
@@ -3466,6 +3469,9 @@ async function trackNoteCommand(idOrLogId, options) {
3466
3469
  if (options.dryRun) {
3467
3470
  body.dryRun = true;
3468
3471
  }
3472
+ if (typeof options.promptVersion === "number") {
3473
+ body.promptVersion = options.promptVersion;
3474
+ }
3469
3475
  return adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/note`, body);
3470
3476
  }
3471
3477
  var DEFAULT_VIDEO_MODEL = "anthropic/claude-opus-4-8", DEFAULT_VIDEO_REASONING = "high", FOUND_BASE = "https://found.fluncle.com", VIDEO_FIELDS, RERENDER_CONTRACT_FIELDS, RERENDER_ADVISORY_FIELDS, FOOTAGE_FIELDS, PLATE_FIELDS, NON_FILE_OPTIONS;
@@ -3546,6 +3552,7 @@ var init_admin_attention = __esm(() => {
3546
3552
  "drip-empty": "clip drip",
3547
3553
  "label-review": "label",
3548
3554
  newsletter: "newsletter",
3555
+ "note-rejected": "held note",
3549
3556
  "post-tiktok": "tiktok",
3550
3557
  "post-youtube": "youtube",
3551
3558
  submission: "submission",
@@ -5017,6 +5024,9 @@ function buildBody2(options, { requireContent }) {
5017
5024
  } else if (requireContent) {
5018
5025
  throw new CliError2("missing_content", "A draft needs the structured content payload via --content-file <edition.json>");
5019
5026
  }
5027
+ if (requireContent && typeof options.promptVersion === "number") {
5028
+ body.promptVersion = options.promptVersion;
5029
+ }
5020
5030
  if (options.subject !== undefined) {
5021
5031
  body.subject = options.subject;
5022
5032
  }
@@ -5094,6 +5104,7 @@ function requireTitle(options) {
5094
5104
  async function logbookCreateCommand(sector, options) {
5095
5105
  return adminApiPost(`/api/admin/logbook/${encodeURIComponent(sector)}`, {
5096
5106
  body: resolveBody(options),
5107
+ ...typeof options.promptVersion === "number" ? { promptVersion: options.promptVersion } : {},
5097
5108
  title: requireTitle(options)
5098
5109
  });
5099
5110
  }
@@ -5108,6 +5119,193 @@ var init_admin_logbook = __esm(() => {
5108
5119
  init_output();
5109
5120
  });
5110
5121
 
5122
+ // src/commands/admin-prompts.ts
5123
+ var exports_admin_prompts = {};
5124
+ __export(exports_admin_prompts, {
5125
+ renderDiff: () => renderDiff,
5126
+ promptsListCommand: () => promptsListCommand,
5127
+ promptUpdateCommand: () => promptUpdateCommand,
5128
+ promptRows: () => promptRows,
5129
+ promptRollbackCommand: () => promptRollbackCommand,
5130
+ promptResetCommand: () => promptResetCommand,
5131
+ promptGetCommand: () => promptGetCommand,
5132
+ promptDiffCommand: () => promptDiffCommand,
5133
+ promptDetailCommand: () => promptDetailCommand,
5134
+ parseVersion: () => parseVersion3,
5135
+ parseAgainst: () => parseAgainst,
5136
+ historyRows: () => historyRows,
5137
+ diffLines: () => diffLines,
5138
+ bodyLines: () => bodyLines
5139
+ });
5140
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
5141
+ async function promptsListCommand() {
5142
+ const response = await adminApiGet("/api/admin/prompts");
5143
+ return response.prompts;
5144
+ }
5145
+ async function promptGetCommand(slug) {
5146
+ return adminApiGet(`/api/admin/prompts/${encodeURIComponent(slug)}`);
5147
+ }
5148
+ async function promptDetailCommand(slug) {
5149
+ const prompts = await promptsListCommand();
5150
+ const detail = prompts.find((prompt) => prompt.slug === slug);
5151
+ if (!detail) {
5152
+ throw new CliError2("unknown_prompt", `No prompt goes by "${slug}". The registered ones: ${prompts.map((prompt) => prompt.slug).join(", ")}`);
5153
+ }
5154
+ return detail;
5155
+ }
5156
+ function resolveBody2(options) {
5157
+ if (options.bodyFile === undefined) {
5158
+ throw new CliError2("missing_body", "An edit needs a body via --body-file <prompt.txt>. Start from `fluncle admin prompts get <slug> --json | jq -r .body`.");
5159
+ }
5160
+ if (!existsSync6(options.bodyFile)) {
5161
+ throw new CliError2("file_not_found", `Body file not found: ${options.bodyFile}`);
5162
+ }
5163
+ const body = readFileSync6(options.bodyFile, "utf-8");
5164
+ if (body.trim().length === 0) {
5165
+ throw new CliError2("empty_body", "A prompt body cannot be empty. Nothing was appended.");
5166
+ }
5167
+ return body;
5168
+ }
5169
+ async function promptUpdateCommand(slug, options) {
5170
+ const body = resolveBody2(options);
5171
+ const note = options.note?.trim();
5172
+ const response = await adminApiPost(`/api/admin/prompts/${encodeURIComponent(slug)}`, note ? { body, note } : { body });
5173
+ return { slug, version: response.version };
5174
+ }
5175
+ async function promptRollbackCommand(slug, version) {
5176
+ const detail = await promptDetailCommand(slug);
5177
+ const target = detail.versions.find((candidate) => candidate.version === version);
5178
+ if (!target) {
5179
+ const known = detail.versions.map((candidate) => `v${candidate.version}`).join(", ");
5180
+ throw new CliError2("unknown_version", known.length > 0 ? `${slug} has no v${version}. On file: ${known}. For the repo's baked default, run \`fluncle admin prompts reset ${slug}\`.` : `${slug} has no history yet: the repo's baked default is what runs. There is nothing to roll back to.`);
5181
+ }
5182
+ return restore(detail, { body: target.body, from: version, note: `rolled back to v${version}` });
5183
+ }
5184
+ async function promptResetCommand(slug) {
5185
+ const detail = await promptDetailCommand(slug);
5186
+ return restore(detail, {
5187
+ body: detail.defaultBody,
5188
+ from: 0,
5189
+ note: "reset to the repo's baked default"
5190
+ });
5191
+ }
5192
+ async function restore(detail, input) {
5193
+ if (input.body.trim() === detail.activeBody.trim()) {
5194
+ return { from: input.from, skipped: true, slug: detail.slug, version: detail.activeVersion };
5195
+ }
5196
+ const response = await adminApiPost(`/api/admin/prompts/${encodeURIComponent(detail.slug)}`, { body: input.body, note: input.note });
5197
+ return { from: input.from, skipped: false, slug: detail.slug, version: response.version };
5198
+ }
5199
+ function parseAgainst(value) {
5200
+ if (value === undefined || value.trim().toLowerCase() === "default") {
5201
+ return { kind: "default" };
5202
+ }
5203
+ const version = parseVersion3(value);
5204
+ if (version === undefined) {
5205
+ throw new CliError2("invalid_against", `--against takes a version (3, or v3) or the word default. Got "${value}".`);
5206
+ }
5207
+ return { kind: "version", version };
5208
+ }
5209
+ function parseVersion3(value) {
5210
+ const digits = /^v?(\d+)$/.exec(value.trim());
5211
+ const parsed = digits?.[1];
5212
+ if (parsed === undefined) {
5213
+ return;
5214
+ }
5215
+ const version = Number.parseInt(parsed, 10);
5216
+ return Number.isFinite(version) && version > 0 ? version : undefined;
5217
+ }
5218
+ function bodyLines(body) {
5219
+ return body.replace(/\r\n/g, `
5220
+ `).replace(/\n+$/, "").split(`
5221
+ `);
5222
+ }
5223
+ function diffLines(before, after) {
5224
+ const rows = before.length;
5225
+ const cols = after.length;
5226
+ const width = cols + 1;
5227
+ const lengths = new Int32Array((rows + 1) * width);
5228
+ const lcs = (row2, col2) => lengths[row2 * width + col2] ?? 0;
5229
+ for (let row2 = rows - 1;row2 >= 0; row2 -= 1) {
5230
+ for (let col2 = cols - 1;col2 >= 0; col2 -= 1) {
5231
+ lengths[row2 * width + col2] = before[row2] === after[col2] ? lcs(row2 + 1, col2 + 1) + 1 : Math.max(lcs(row2 + 1, col2), lcs(row2, col2 + 1));
5232
+ }
5233
+ }
5234
+ const lines = [];
5235
+ let row = 0;
5236
+ let col = 0;
5237
+ while (row < rows && col < cols) {
5238
+ if (before[row] === after[col]) {
5239
+ lines.push({ kind: "context", text: before[row] ?? "" });
5240
+ row += 1;
5241
+ col += 1;
5242
+ continue;
5243
+ }
5244
+ if (lcs(row + 1, col) >= lcs(row, col + 1)) {
5245
+ lines.push({ kind: "remove", text: before[row] ?? "" });
5246
+ row += 1;
5247
+ continue;
5248
+ }
5249
+ lines.push({ kind: "add", text: after[col] ?? "" });
5250
+ col += 1;
5251
+ }
5252
+ while (row < rows) {
5253
+ lines.push({ kind: "remove", text: before[row] ?? "" });
5254
+ row += 1;
5255
+ }
5256
+ while (col < cols) {
5257
+ lines.push({ kind: "add", text: after[col] ?? "" });
5258
+ col += 1;
5259
+ }
5260
+ return lines;
5261
+ }
5262
+ function renderDiff(lines) {
5263
+ return lines.map((line) => {
5264
+ const marker = line.kind === "add" ? "+" : line.kind === "remove" ? "-" : " ";
5265
+ return `${marker} ${line.text}`;
5266
+ });
5267
+ }
5268
+ async function promptDiffCommand(slug, against) {
5269
+ const detail = await promptDetailCommand(slug);
5270
+ const from = against.kind === "default" ? { body: detail.defaultBody, label: "the repo's baked default", version: 0 } : resolveAgainstVersion(detail, against.version);
5271
+ const lines = diffLines(bodyLines(from.body), bodyLines(detail.activeBody));
5272
+ return {
5273
+ added: lines.filter((line) => line.kind === "add").length,
5274
+ against: { label: from.label, version: from.version },
5275
+ lines,
5276
+ live: { source: detail.source, version: detail.activeVersion },
5277
+ removed: lines.filter((line) => line.kind === "remove").length,
5278
+ slug: detail.slug
5279
+ };
5280
+ }
5281
+ function resolveAgainstVersion(detail, version) {
5282
+ const target = detail.versions.find((candidate) => candidate.version === version);
5283
+ if (!target) {
5284
+ const known = detail.versions.map((candidate) => `v${candidate.version}`).join(", ");
5285
+ throw new CliError2("unknown_version", known.length > 0 ? `${detail.slug} has no v${version}. On file: ${known}.` : `${detail.slug} has no history yet. Diff against the repo's default instead: drop --against.`);
5286
+ }
5287
+ return { body: target.body, label: `v${version}`, version };
5288
+ }
5289
+ function promptRows(prompts) {
5290
+ const slugWidth = prompts.reduce((width, prompt) => Math.max(width, prompt.slug.length), 0);
5291
+ const surfaceWidth = prompts.reduce((width, prompt) => Math.max(width, prompt.surface.length), 0);
5292
+ return prompts.map((prompt) => {
5293
+ const live = prompt.source === "override" ? `v${prompt.activeVersion}` : "default";
5294
+ return `${prompt.slug.padEnd(slugWidth)} ${prompt.surface.padEnd(surfaceWidth)} ${live.padEnd(7)} ${prompt.title}`;
5295
+ });
5296
+ }
5297
+ function historyRows(versions) {
5298
+ return versions.map((version) => {
5299
+ const when = version.createdAt.slice(0, 10);
5300
+ const note = version.note?.trim();
5301
+ return `v${version.version} ${when} ${version.createdBy.padEnd(8)} ${note && note.length > 0 ? note : "(no note)"}`;
5302
+ });
5303
+ }
5304
+ var init_admin_prompts = __esm(() => {
5305
+ init_api();
5306
+ init_output();
5307
+ });
5308
+
5111
5309
  // src/commands/add.ts
5112
5310
  async function addCommand2(spotifyUrl, options) {
5113
5311
  const result = await adminApiPost("/api/admin/tracks", {
@@ -5196,7 +5394,10 @@ async function approveSubmissionCommand(submissionId, options = {}) {
5196
5394
  console.log(`Approved ${formatTrackLine(submission)}.`);
5197
5395
  }
5198
5396
  async function triageSubmissionCommand(submissionId, verdict, options = {}) {
5199
- const response = await adminApiPost(`/api/admin/submissions/${encodeURIComponent(submissionId)}/triage`, { verdict });
5397
+ const response = await adminApiPost(`/api/admin/submissions/${encodeURIComponent(submissionId)}/triage`, {
5398
+ ...typeof options.promptVersion === "number" ? { promptVersion: options.promptVersion } : {},
5399
+ verdict
5400
+ });
5200
5401
  if (options.json) {
5201
5402
  printJson2({ ok: true, submission: response.submission });
5202
5403
  return;
@@ -5445,6 +5646,35 @@ var init_auth_lastfm = __esm(() => {
5445
5646
  init_api();
5446
5647
  });
5447
5648
 
5649
+ // src/commands/admin-notes.ts
5650
+ var exports_admin_notes = {};
5651
+ __export(exports_admin_notes, {
5652
+ noteHeldCommand: () => noteHeldCommand,
5653
+ noteGateCommand: () => noteGateCommand
5654
+ });
5655
+ async function noteHeldCommand(options) {
5656
+ const query = options.settled ? "?open=false" : "";
5657
+ return adminApiGet(`/api/admin/note-rejections${query}`);
5658
+ }
5659
+ async function noteGateCommand(options) {
5660
+ const patch = {};
5661
+ if (options.minPhraseWords !== undefined) {
5662
+ patch.minPhraseWords = options.minPhraseWords;
5663
+ }
5664
+ if (options.maxOverlap !== undefined) {
5665
+ patch.maxOverlap = options.maxOverlap;
5666
+ }
5667
+ if (Object.keys(patch).length === 0) {
5668
+ const response2 = await adminApiGet("/api/admin/note-rejections");
5669
+ return response2.gate;
5670
+ }
5671
+ const response = await adminApiPatch("/api/admin/note-gate", patch);
5672
+ return response.gate;
5673
+ }
5674
+ var init_admin_notes = __esm(() => {
5675
+ init_api();
5676
+ });
5677
+
5448
5678
  // src/commands/admin-catalogue.ts
5449
5679
  var exports_admin_catalogue = {};
5450
5680
  __export(exports_admin_catalogue, {
@@ -5815,7 +6045,7 @@ var init_format2 = __esm(() => {
5815
6045
  });
5816
6046
 
5817
6047
  // src/cli.ts
5818
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
6048
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
5819
6049
  import path2 from "path";
5820
6050
 
5821
6051
  // ../../node_modules/commander/lib/error.js
@@ -8180,7 +8410,7 @@ function addAdminCommands(program2) {
8180
8410
  const { previewArchiveUploadCommand: previewArchiveUploadCommand2 } = await Promise.resolve().then(() => (init_preview_archive(), exports_preview_archive));
8181
8411
  await runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCommand2);
8182
8412
  });
8183
- adminTrack.command("observe").description("Render Fluncle's spoken field observation for a track (Cartesia, Worker-side)").argument("[idOrLogId]").option("--queue", "Show the observe worklist (notes but no observation yet), oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--script <text>", "The voice-gated observation script (the spoken text)").option("--script-file <file>", "Read the observation script from a file (e.g. observation.txt)").option("--voice-id <id>", "Override the configured Cartesia voice id").option("--duration-ms <ms>", "Probed audio duration in ms (else derived from word timestamps)").option("--duration-target-sec <sec>", "Target observation length in seconds (20\u201345)").option("--context-note <text>", "Pre-fetched factual context (else the Worker firecrawls)").option("--force", "Re-render even if an observation already exists (voice re-tune / fix)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
8413
+ adminTrack.command("observe").description("Render Fluncle's spoken field observation for a track (Cartesia, Worker-side)").argument("[idOrLogId]").option("--queue", "Show the observe worklist (notes but no observation yet), oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--script <text>", "The voice-gated observation script (the spoken text)").option("--script-file <file>", "Read the observation script from a file (e.g. observation.txt)").option("--voice-id <id>", "Override the configured Cartesia voice id").option("--duration-ms <ms>", "Probed audio duration in ms (else derived from word timestamps)").option("--duration-target-sec <sec>", "Target observation length in seconds (20\u201345)").option("--context-note <text>", "Pre-fetched factual context (else the Worker firecrawls)").option("--prompt-version <n>", "The prompt-registry version that authored this (the sweep sends it; it is the artifact's provenance)").option("--force", "Re-render even if an observation already exists (voice re-tune / fix)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
8184
8414
  if (options.queue) {
8185
8415
  const { observeQueueCommand: observeQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
8186
8416
  await runAdminObserveQueue(options, observeQueueCommand2);
@@ -8198,7 +8428,7 @@ function addAdminCommands(program2) {
8198
8428
  const { trackContextCommand: trackContextCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
8199
8429
  await runTrackContext(idOrLogId, options, trackContextCommand2);
8200
8430
  });
8201
- adminTrack.command("note").description("Author the editorial note for a finding (fills an empty note only)").argument("[idOrLogId]").option("--queue", "Show the note worklist (context'd findings with no note yet), oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--script <text>", "The voice-gated editorial note").option("--script-file <file>", "Read the editorial note from a file").option("--dry-run", "Run the voice + echo gates and report the verdict without storing anything", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
8431
+ adminTrack.command("note").description("Author the editorial note for a finding (fills an empty note only)").argument("[idOrLogId]").option("--queue", "Show the note worklist (context'd findings with no note yet), oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--script <text>", "The voice-gated editorial note").option("--script-file <file>", "Read the editorial note from a file").option("--prompt-version <n>", "The prompt-registry version that authored the note (the sweep sends this; it is the note's provenance)").option("--dry-run", "Run the voice + echo gates and report the verdict without storing anything", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
8202
8432
  if (options.queue) {
8203
8433
  const { noteQueueCommand: noteQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
8204
8434
  await runAdminNoteQueue(options, noteQueueCommand2);
@@ -8331,9 +8561,9 @@ function addAdminCommands(program2) {
8331
8561
  adminNewsletter.action(() => {
8332
8562
  adminNewsletter.outputHelp();
8333
8563
  });
8334
- adminNewsletter.command("draft").description("Persist a newsletter edition draft (the agent authors it, you send it)").option("--content-file <file>", "Structured edition content payload (JSON)").option("--subject <text>", "Email subject line").option("--window-since <date>", "Discovery-window start (ISO)").option("--window-until <date>", "Discovery-window end (ISO)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
8564
+ adminNewsletter.command("draft").description("Persist a newsletter edition draft (the agent authors it, you send it)").option("--content-file <file>", "Structured edition content payload (JSON)").option("--subject <text>", "Email subject line").option("--window-since <date>", "Discovery-window start (ISO)").option("--window-until <date>", "Discovery-window end (ISO)").option("--prompt-version <n>", "The prompt-registry version that authored this (the sweep sends it; it is the edition's provenance)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
8335
8565
  const { newsletterDraftCommand: newsletterDraftCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
8336
- await runNewsletterDraft(options, newsletterDraftCommand2);
8566
+ await runNewsletterDraft({ ...options, promptVersion: parsePromptVersion(options.promptVersion) }, newsletterDraftCommand2);
8337
8567
  });
8338
8568
  adminNewsletter.command("update").description("Update a draft edition's payload, subject, or window").argument("[id]").option("--content-file <file>", "Structured edition content payload (JSON)").option("--subject <text>", "Email subject line").option("--window-since <date>", "Discovery-window start (ISO)").option("--window-until <date>", "Discovery-window end (ISO)").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
8339
8569
  const { newsletterUpdateCommand: newsletterUpdateCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
@@ -8359,7 +8589,7 @@ function addAdminCommands(program2) {
8359
8589
  const { logbookGapsCommand: logbookGapsCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8360
8590
  await runLogbookGaps(options, logbookGapsCommand2);
8361
8591
  });
8362
- 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) => {
8592
+ 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("--prompt-version <n>", "The prompt-registry version that authored this (the sweep sends it; it is the artifact's provenance)").option("--json", "Print JSON", false).allowExcessArguments().action(async (sector, options) => {
8363
8593
  const { logbookCreateCommand: logbookCreateCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8364
8594
  await runLogbookWrite(sector, options, logbookCreateCommand2);
8365
8595
  });
@@ -8367,6 +8597,38 @@ function addAdminCommands(program2) {
8367
8597
  const { logbookUpdateCommand: logbookUpdateCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8368
8598
  await runLogbookWrite(sector, options, logbookUpdateCommand2);
8369
8599
  });
8600
+ const adminPrompts = configureCommand(admin.command("prompts").description("The prompt registry: what Fluncle tells the models"));
8601
+ adminPrompts.action(() => {
8602
+ adminPrompts.outputHelp();
8603
+ });
8604
+ adminPrompts.command("list").description("Every prompt: where it runs, and whether the repo default or an edit is live").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
8605
+ const { promptsListCommand: promptsListCommand2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
8606
+ await runPromptsList(options, promptsListCommand2);
8607
+ });
8608
+ adminPrompts.command("get").description("The body running right now, with its version and source").argument("[slug]").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
8609
+ const { promptGetCommand: promptGetCommand2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
8610
+ await runPromptGet(slug, options, promptGetCommand2);
8611
+ });
8612
+ adminPrompts.command("history").description("Every version of a prompt, newest first: when, who, and the why").argument("[slug]").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
8613
+ const { promptDetailCommand: promptDetailCommand2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
8614
+ await runPromptHistory(slug, options, promptDetailCommand2);
8615
+ });
8616
+ adminPrompts.command("diff").description("Line diff: the live body against the repo default, or against a version").argument("[slug]").option("--against <version>", "A version (3, or v3) or the word default", "default").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
8617
+ const { parseAgainst: parseAgainst2, promptDiffCommand: promptDiffCommand2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
8618
+ await runPromptDiff(slug, options, { parseAgainst: parseAgainst2, promptDiffCommand: promptDiffCommand2 });
8619
+ });
8620
+ adminPrompts.command("update").description("Append an edited body as a new version. OPERATOR only").argument("[slug]").option("--body-file <file>", "Read the new prompt body from a file").option("--note <text>", 'The why, for the history ("shortened the neighbour block")').option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
8621
+ const { promptUpdateCommand: promptUpdateCommand2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
8622
+ await runPromptUpdate(slug, options, promptUpdateCommand2);
8623
+ });
8624
+ adminPrompts.command("rollback").description("Put an old version's body back, as a new version. OPERATOR only").argument("[slug]").argument("[version]").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, version, options) => {
8625
+ const { parseVersion: parseVersion4, promptRollbackCommand: promptRollbackCommand2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
8626
+ await runPromptRollback(slug, version, options, { parseVersion: parseVersion4, promptRollbackCommand: promptRollbackCommand2 });
8627
+ });
8628
+ adminPrompts.command("reset").description("Put the repo's baked default back, as a new version. OPERATOR only").argument("[slug]").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
8629
+ const { promptResetCommand: promptResetCommand2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
8630
+ await runPromptReset(slug, options, promptResetCommand2);
8631
+ });
8370
8632
  const submissions = configureCommand(admin.command("submissions").description("Review listener submissions"));
8371
8633
  submissions.option("--json", "Print JSON", false).action(async (options) => {
8372
8634
  const { listSubmissionsCommand: listSubmissionsCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
@@ -8393,16 +8655,19 @@ function addAdminCommands(program2) {
8393
8655
  const { approveSubmissionCommand: approveSubmissionCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
8394
8656
  await approveSubmissionCommand2(submissionId, options);
8395
8657
  });
8396
- 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) => {
8658
+ 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("--prompt-version <n>", "The prompt-registry version that authored this (the sweep sends it; it is the artifact's provenance)").option("--json", "Print JSON", false).action(async (submissionId, options) => {
8397
8659
  if (!submissionId) {
8398
8660
  throw new Error("Missing submission id for: triage");
8399
8661
  }
8400
- const verdict = options.verdictFile ? readFileSync6(options.verdictFile, "utf8") : options.verdict;
8662
+ const verdict = options.verdictFile ? readFileSync7(options.verdictFile, "utf8") : options.verdict;
8401
8663
  if (!verdict || !verdict.trim()) {
8402
8664
  throw new Error("Usage: fluncle admin submissions triage <submissionId> (--verdict <text> | --verdict-file <file>) [--json]");
8403
8665
  }
8404
8666
  const { triageSubmissionCommand: triageSubmissionCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
8405
- await triageSubmissionCommand2(submissionId, verdict, { json: options.json });
8667
+ await triageSubmissionCommand2(submissionId, verdict, {
8668
+ json: options.json,
8669
+ promptVersion: parsePromptVersion(options.promptVersion)
8670
+ });
8406
8671
  });
8407
8672
  const auth = configureCommand(admin.command("auth").description("Authentication commands"));
8408
8673
  auth.command("spotify").description("Authorize Spotify access").action(async () => {
@@ -8441,6 +8706,18 @@ function addAdminCommands(program2) {
8441
8706
  const { galaxyMapWriteCommand: galaxyMapWriteCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
8442
8707
  await runGalaxyMapWrite(options, galaxyMapWriteCommand2);
8443
8708
  });
8709
+ const notes = configureCommand(admin.command("notes").description("The auto-note echo gate: held notes + its dials"));
8710
+ notes.action(() => {
8711
+ notes.outputHelp();
8712
+ });
8713
+ notes.command("held").description("The auto-notes the echo gate held back (and why)").option("--settled", "Read the ones already ruled on instead (the retune evidence)", false).option("--json", "Print JSON", false).action(async (options) => {
8714
+ const { noteHeldCommand: noteHeldCommand2 } = await Promise.resolve().then(() => (init_admin_notes(), exports_admin_notes));
8715
+ await runNoteHeld(options, noteHeldCommand2);
8716
+ });
8717
+ notes.command("gate").description("Read or retune the echo gate's thresholds (a flip, not a deploy)").option("--min-phrase-words <n>", "A shared run this long is a lift (2-20; default 4)").option("--max-overlap <x>", "Content-word overlap at/above this is an echo (0.05-1; default 0.3)").option("--json", "Print JSON", false).action(async (options) => {
8718
+ const { noteGateCommand: noteGateCommand2 } = await Promise.resolve().then(() => (init_admin_notes(), exports_admin_notes));
8719
+ await runNoteGate(options, noteGateCommand2);
8720
+ });
8444
8721
  const catalogue = configureCommand(admin.command("catalogue").description("The catalogue (uncertified tracks): the crawler + its ranking"));
8445
8722
  catalogue.action(() => {
8446
8723
  catalogue.outputHelp();
@@ -8522,7 +8799,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
8522
8799
  console.log(` mime: ${result.mime}`);
8523
8800
  }
8524
8801
  async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
8525
- const script = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
8802
+ const script = options.scriptFile ? readFileSync7(options.scriptFile, "utf8") : options.script;
8526
8803
  if (!idOrLogId || !script || !script.trim()) {
8527
8804
  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]");
8528
8805
  }
@@ -8539,6 +8816,7 @@ async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
8539
8816
  durationMs,
8540
8817
  durationTargetSec,
8541
8818
  force: options.force,
8819
+ promptVersion: parsePromptVersion(options.promptVersion),
8542
8820
  script: script.trim(),
8543
8821
  voiceId: options.voiceId
8544
8822
  });
@@ -8577,14 +8855,22 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
8577
8855
  console.log(` sources: ${result.sources.join(", ")}`);
8578
8856
  }
8579
8857
  }
8858
+ function parsePromptVersion(raw) {
8859
+ if (raw === undefined) {
8860
+ return;
8861
+ }
8862
+ const parsed = Number(raw);
8863
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : undefined;
8864
+ }
8580
8865
  async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
8581
- const note = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
8866
+ const note = options.scriptFile ? readFileSync7(options.scriptFile, "utf8") : options.script;
8582
8867
  if (!idOrLogId || !note || !note.trim()) {
8583
8868
  throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--dry-run] [--json]");
8584
8869
  }
8585
8870
  const result = await trackNoteCommand2(idOrLogId, {
8586
8871
  dryRun: options.dryRun,
8587
- note: note.trim()
8872
+ note: note.trim(),
8873
+ promptVersion: parsePromptVersion(options.promptVersion)
8588
8874
  });
8589
8875
  if (options.json) {
8590
8876
  printJson(result);
@@ -9017,7 +9303,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
9017
9303
  return;
9018
9304
  }
9019
9305
  const candidate = path2.join(dir, name);
9020
- return existsSync6(candidate) ? candidate : undefined;
9306
+ return existsSync7(candidate) ? candidate : undefined;
9021
9307
  };
9022
9308
  const resolveFile = (explicit, name) => {
9023
9309
  if (explicit) {
@@ -9229,7 +9515,7 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand3) {
9229
9515
  if (options.analyzedFrom !== undefined && options.analyzedFrom !== "full" && options.analyzedFrom !== "preview") {
9230
9516
  throw new Error(`Invalid --analyzed-from: ${options.analyzedFrom} (expected full or preview)`);
9231
9517
  }
9232
- const embeddingRaw = options.embeddingFile ? readFileSync6(options.embeddingFile, "utf8") : options.embedding;
9518
+ const embeddingRaw = options.embeddingFile ? readFileSync7(options.embeddingFile, "utf8") : options.embedding;
9233
9519
  const embedding = parseEmbeddingArg(embeddingRaw);
9234
9520
  const result = await trackUpdateCommand3(trackId, {
9235
9521
  analyzedAt: options.analyzedAt,
@@ -9602,6 +9888,7 @@ async function runLogbookWrite(sector, options, writeCommand) {
9602
9888
  const result = await writeCommand(sector, {
9603
9889
  body: options.body,
9604
9890
  bodyFile: options.bodyFile,
9891
+ promptVersion: parsePromptVersion(options.promptVersion),
9605
9892
  title: options.title
9606
9893
  });
9607
9894
  if (options.json) {
@@ -9611,6 +9898,109 @@ async function runLogbookWrite(sector, options, writeCommand) {
9611
9898
  const skipped = "skipped" in result && result.skipped;
9612
9899
  console.log(skipped ? `sector ${result.entry.sector}: an entry already stands \u2014 no-op (${result.entry.generatedBy})` : `sector ${result.entry.sector}: ${result.entry.title}`);
9613
9900
  }
9901
+ function requirePromptSlug(slug) {
9902
+ if (!slug) {
9903
+ throw new Error("Missing prompt slug. `fluncle admin prompts list` names them all.");
9904
+ }
9905
+ return slug;
9906
+ }
9907
+ async function runPromptsList(options, promptsListCommand2) {
9908
+ const prompts = await promptsListCommand2();
9909
+ if (options.json) {
9910
+ printJson({ ok: true, prompts });
9911
+ return;
9912
+ }
9913
+ const { promptRows: promptRows2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
9914
+ for (const row of promptRows2(prompts)) {
9915
+ console.log(row);
9916
+ }
9917
+ }
9918
+ async function runPromptGet(slug, options, promptGetCommand2) {
9919
+ const resolved = await promptGetCommand2(requirePromptSlug(slug));
9920
+ if (options.json) {
9921
+ printJson(resolved);
9922
+ return;
9923
+ }
9924
+ const live = resolved.source === "override" ? `v${resolved.version}, an operator edit` : "the repo's baked default (v0)";
9925
+ console.log(`${resolved.slug}: ${live}`);
9926
+ console.log("");
9927
+ console.log(resolved.body);
9928
+ }
9929
+ async function runPromptHistory(slug, options, promptDetailCommand2) {
9930
+ const detail = await promptDetailCommand2(requirePromptSlug(slug));
9931
+ if (options.json) {
9932
+ printJson({
9933
+ activeVersion: detail.activeVersion,
9934
+ ok: true,
9935
+ slug: detail.slug,
9936
+ source: detail.source,
9937
+ versions: detail.versions
9938
+ });
9939
+ return;
9940
+ }
9941
+ if (detail.versions.length === 0) {
9942
+ console.log(`${detail.slug}: never edited. The repo's baked default is what runs.`);
9943
+ return;
9944
+ }
9945
+ const { historyRows: historyRows2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
9946
+ for (const row of historyRows2(detail.versions)) {
9947
+ console.log(row);
9948
+ }
9949
+ }
9950
+ async function runPromptDiff(slug, options, commands) {
9951
+ const against = commands.parseAgainst(options.against);
9952
+ const result = await commands.promptDiffCommand(requirePromptSlug(slug), against);
9953
+ if (options.json) {
9954
+ printJson({ ok: true, ...result });
9955
+ return;
9956
+ }
9957
+ const live = result.live.source === "override" ? `v${result.live.version}` : "the repo's baked default";
9958
+ if (result.added === 0 && result.removed === 0) {
9959
+ console.log(`${result.slug}: ${live} and ${result.against.label} are the same body.`);
9960
+ return;
9961
+ }
9962
+ const { renderDiff: renderDiff2 } = await Promise.resolve().then(() => (init_admin_prompts(), exports_admin_prompts));
9963
+ console.log(`${result.slug}: ${result.against.label} (-) against ${live} (+)`);
9964
+ for (const line of renderDiff2(result.lines)) {
9965
+ console.log(line);
9966
+ }
9967
+ console.log(`${result.added} added, ${result.removed} removed.`);
9968
+ }
9969
+ async function runPromptUpdate(slug, options, promptUpdateCommand2) {
9970
+ const result = await promptUpdateCommand2(requirePromptSlug(slug), {
9971
+ bodyFile: options.bodyFile,
9972
+ note: options.note
9973
+ });
9974
+ if (options.json) {
9975
+ printJson({ ok: true, ...result });
9976
+ return;
9977
+ }
9978
+ console.log(`${result.slug}: v${result.version} is live. Nothing to deploy.`);
9979
+ }
9980
+ async function runPromptRollback(slug, version, options, commands) {
9981
+ const promptSlug = requirePromptSlug(slug);
9982
+ if (!version) {
9983
+ throw new Error(`Missing version. \`fluncle admin prompts history ${promptSlug}\` lists what you can go back to.`);
9984
+ }
9985
+ const parsed = commands.parseVersion(version);
9986
+ if (parsed === undefined) {
9987
+ throw new Error(`A version reads as 3 or v3. Got "${version}".`);
9988
+ }
9989
+ const result = await commands.promptRollbackCommand(promptSlug, parsed);
9990
+ if (options.json) {
9991
+ printJson({ ok: true, ...result });
9992
+ return;
9993
+ }
9994
+ console.log(result.skipped ? `${result.slug}: v${result.from} is already the body running. Nothing appended.` : `${result.slug}: back on v${result.from}'s body, live as v${result.version}.`);
9995
+ }
9996
+ async function runPromptReset(slug, options, promptResetCommand2) {
9997
+ const result = await promptResetCommand2(requirePromptSlug(slug));
9998
+ if (options.json) {
9999
+ printJson({ ok: true, ...result });
10000
+ return;
10001
+ }
10002
+ console.log(result.skipped ? `${result.slug}: already running the repo's baked default. Nothing appended.` : `${result.slug}: back on the repo's baked default, live as v${result.version}.`);
10003
+ }
9614
10004
  async function runAdd(spotifyUrl, options, addCommand3) {
9615
10005
  if (!spotifyUrl) {
9616
10006
  throw new Error("Missing Spotify track URL");
@@ -9753,6 +10143,44 @@ async function runCatalogueList(options, catalogueListCommand2) {
9753
10143
  console.log(`${score} ${identity.padEnd(52)} ${why}`);
9754
10144
  }
9755
10145
  }
10146
+ async function runNoteHeld(options, noteHeldCommand2) {
10147
+ const { gate, rejections } = await noteHeldCommand2({ settled: options.settled === true });
10148
+ if (options.json) {
10149
+ printJson({ gate, ok: true, rejections });
10150
+ return;
10151
+ }
10152
+ if (rejections.length === 0) {
10153
+ console.log(options.settled ? "Nothing ruled on yet." : "Nothing held back. The gate is quiet.");
10154
+ return;
10155
+ }
10156
+ console.log(`The gate sits at ${gate.minPhraseWords} lifted words / ${Math.round(gate.maxOverlap * 100)}% overlap.`);
10157
+ for (const held of rejections) {
10158
+ const ruled = held.resolution ? ` [${held.resolution}]` : "";
10159
+ const bounced = held.attempts > 1 ? ` (bounced ${held.attempts}x)` : "";
10160
+ console.log("");
10161
+ console.log(`${held.logId ?? held.trackId} ${held.title}${bounced}${ruled}`);
10162
+ console.log(` wrote: ${held.note}`);
10163
+ if (held.phrase) {
10164
+ console.log(` lifted: "${held.phrase}" from ${held.neighborLogId ?? "a neighbour"}`);
10165
+ } else {
10166
+ console.log(` reuses: ${Math.round(held.overlap * 100)}% of ${held.neighborLogId ?? "a neighbour"}'s words`);
10167
+ }
10168
+ console.log(` judged at: ${held.minPhraseWords} words / ${Math.round(held.maxOverlap * 100)}%`);
10169
+ }
10170
+ }
10171
+ async function runNoteGate(options, noteGateCommand2) {
10172
+ const minPhraseWords = options.minPhraseWords === undefined ? undefined : Number(options.minPhraseWords);
10173
+ const maxOverlap = options.maxOverlap === undefined ? undefined : Number(options.maxOverlap);
10174
+ const gate = await noteGateCommand2({
10175
+ ...maxOverlap === undefined ? {} : { maxOverlap },
10176
+ ...minPhraseWords === undefined ? {} : { minPhraseWords }
10177
+ });
10178
+ if (options.json) {
10179
+ printJson({ gate, ok: true });
10180
+ return;
10181
+ }
10182
+ console.log(`The echo gate: a lift is ${gate.minPhraseWords}+ shared words; an echo is ${Math.round(gate.maxOverlap * 100)}%+ of a neighbour's words.`);
10183
+ }
9756
10184
  async function runGalaxyMapRead(options, galaxyMapReadCommand2) {
9757
10185
  const galaxies = await galaxyMapReadCommand2();
9758
10186
  if (options.json) {
@@ -9778,7 +10206,7 @@ async function runGalaxyEmbeddings(options, galaxyEmbeddingsCommand2) {
9778
10206
  console.log(`${result.embeddings.length} embedding(s)${result.nextCursor ? ` \xB7 nextCursor: ${result.nextCursor}` : " \xB7 end of corpus"}`);
9779
10207
  }
9780
10208
  async function runGalaxyMapWrite(options, galaxyMapWriteCommand2) {
9781
- const raw = JSON.parse(readFileSync6(options.file, "utf8"));
10209
+ const raw = JSON.parse(readFileSync7(options.file, "utf8"));
9782
10210
  const clusters = Array.isArray(raw) || raw === null ? raw : raw.clusters;
9783
10211
  if (!Array.isArray(clusters)) {
9784
10212
  throw new Error("Invalid --file: expected { clusters: [...] } or a JSON array of cluster rows");
@@ -10320,6 +10748,7 @@ function normalizeCommanderError(error) {
10320
10748
  return new Error(message);
10321
10749
  }
10322
10750
  var stringOptions = new Set([
10751
+ "--against",
10323
10752
  "--analyzed-at",
10324
10753
  "--analyzed-from",
10325
10754
  "--at",
@@ -10359,8 +10788,10 @@ var stringOptions = new Set([
10359
10788
  "--lens",
10360
10789
  "--limit",
10361
10790
  "--max-hop",
10791
+ "--max-overlap",
10362
10792
  "--metrics",
10363
10793
  "--mime",
10794
+ "--min-phrase-words",
10364
10795
  "--model",
10365
10796
  "--note",
10366
10797
  "--order",
@@ -10369,6 +10800,7 @@ var stringOptions = new Set([
10369
10800
  "--plate-background",
10370
10801
  "--platform",
10371
10802
  "--poster",
10803
+ "--prompt-version",
10372
10804
  "--props",
10373
10805
  "--query",
10374
10806
  "--reasoning",
@@ -10377,9 +10809,9 @@ var stringOptions = new Set([
10377
10809
  "--render",
10378
10810
  "--scene",
10379
10811
  "--scheduled-for",
10812
+ "--scope",
10380
10813
  "--script",
10381
10814
  "--script-file",
10382
- "--scope",
10383
10815
  "--seed",
10384
10816
  "--soundcloud-url",
10385
10817
  "--source",
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.134.0"
34
+ "version": "0.136.0"
35
35
  }