fluncle 0.135.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 +368 -18
  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.135.0".trim() ? "0.135.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.135.0".trim() ? "0.135.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;
@@ -5018,6 +5024,9 @@ function buildBody2(options, { requireContent }) {
5018
5024
  } else if (requireContent) {
5019
5025
  throw new CliError2("missing_content", "A draft needs the structured content payload via --content-file <edition.json>");
5020
5026
  }
5027
+ if (requireContent && typeof options.promptVersion === "number") {
5028
+ body.promptVersion = options.promptVersion;
5029
+ }
5021
5030
  if (options.subject !== undefined) {
5022
5031
  body.subject = options.subject;
5023
5032
  }
@@ -5095,6 +5104,7 @@ function requireTitle(options) {
5095
5104
  async function logbookCreateCommand(sector, options) {
5096
5105
  return adminApiPost(`/api/admin/logbook/${encodeURIComponent(sector)}`, {
5097
5106
  body: resolveBody(options),
5107
+ ...typeof options.promptVersion === "number" ? { promptVersion: options.promptVersion } : {},
5098
5108
  title: requireTitle(options)
5099
5109
  });
5100
5110
  }
@@ -5109,6 +5119,193 @@ var init_admin_logbook = __esm(() => {
5109
5119
  init_output();
5110
5120
  });
5111
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
+
5112
5309
  // src/commands/add.ts
5113
5310
  async function addCommand2(spotifyUrl, options) {
5114
5311
  const result = await adminApiPost("/api/admin/tracks", {
@@ -5197,7 +5394,10 @@ async function approveSubmissionCommand(submissionId, options = {}) {
5197
5394
  console.log(`Approved ${formatTrackLine(submission)}.`);
5198
5395
  }
5199
5396
  async function triageSubmissionCommand(submissionId, verdict, options = {}) {
5200
- 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
+ });
5201
5401
  if (options.json) {
5202
5402
  printJson2({ ok: true, submission: response.submission });
5203
5403
  return;
@@ -5845,7 +6045,7 @@ var init_format2 = __esm(() => {
5845
6045
  });
5846
6046
 
5847
6047
  // src/cli.ts
5848
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
6048
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
5849
6049
  import path2 from "path";
5850
6050
 
5851
6051
  // ../../node_modules/commander/lib/error.js
@@ -8210,7 +8410,7 @@ function addAdminCommands(program2) {
8210
8410
  const { previewArchiveUploadCommand: previewArchiveUploadCommand2 } = await Promise.resolve().then(() => (init_preview_archive(), exports_preview_archive));
8211
8411
  await runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCommand2);
8212
8412
  });
8213
- 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) => {
8214
8414
  if (options.queue) {
8215
8415
  const { observeQueueCommand: observeQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
8216
8416
  await runAdminObserveQueue(options, observeQueueCommand2);
@@ -8228,7 +8428,7 @@ function addAdminCommands(program2) {
8228
8428
  const { trackContextCommand: trackContextCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
8229
8429
  await runTrackContext(idOrLogId, options, trackContextCommand2);
8230
8430
  });
8231
- 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) => {
8232
8432
  if (options.queue) {
8233
8433
  const { noteQueueCommand: noteQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
8234
8434
  await runAdminNoteQueue(options, noteQueueCommand2);
@@ -8361,9 +8561,9 @@ function addAdminCommands(program2) {
8361
8561
  adminNewsletter.action(() => {
8362
8562
  adminNewsletter.outputHelp();
8363
8563
  });
8364
- 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) => {
8365
8565
  const { newsletterDraftCommand: newsletterDraftCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
8366
- await runNewsletterDraft(options, newsletterDraftCommand2);
8566
+ await runNewsletterDraft({ ...options, promptVersion: parsePromptVersion(options.promptVersion) }, newsletterDraftCommand2);
8367
8567
  });
8368
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) => {
8369
8569
  const { newsletterUpdateCommand: newsletterUpdateCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
@@ -8389,7 +8589,7 @@ function addAdminCommands(program2) {
8389
8589
  const { logbookGapsCommand: logbookGapsCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8390
8590
  await runLogbookGaps(options, logbookGapsCommand2);
8391
8591
  });
8392
- 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) => {
8393
8593
  const { logbookCreateCommand: logbookCreateCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8394
8594
  await runLogbookWrite(sector, options, logbookCreateCommand2);
8395
8595
  });
@@ -8397,6 +8597,38 @@ function addAdminCommands(program2) {
8397
8597
  const { logbookUpdateCommand: logbookUpdateCommand2 } = await Promise.resolve().then(() => (init_admin_logbook(), exports_admin_logbook));
8398
8598
  await runLogbookWrite(sector, options, logbookUpdateCommand2);
8399
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
+ });
8400
8632
  const submissions = configureCommand(admin.command("submissions").description("Review listener submissions"));
8401
8633
  submissions.option("--json", "Print JSON", false).action(async (options) => {
8402
8634
  const { listSubmissionsCommand: listSubmissionsCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
@@ -8423,16 +8655,19 @@ function addAdminCommands(program2) {
8423
8655
  const { approveSubmissionCommand: approveSubmissionCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
8424
8656
  await approveSubmissionCommand2(submissionId, options);
8425
8657
  });
8426
- 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) => {
8427
8659
  if (!submissionId) {
8428
8660
  throw new Error("Missing submission id for: triage");
8429
8661
  }
8430
- const verdict = options.verdictFile ? readFileSync6(options.verdictFile, "utf8") : options.verdict;
8662
+ const verdict = options.verdictFile ? readFileSync7(options.verdictFile, "utf8") : options.verdict;
8431
8663
  if (!verdict || !verdict.trim()) {
8432
8664
  throw new Error("Usage: fluncle admin submissions triage <submissionId> (--verdict <text> | --verdict-file <file>) [--json]");
8433
8665
  }
8434
8666
  const { triageSubmissionCommand: triageSubmissionCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
8435
- await triageSubmissionCommand2(submissionId, verdict, { json: options.json });
8667
+ await triageSubmissionCommand2(submissionId, verdict, {
8668
+ json: options.json,
8669
+ promptVersion: parsePromptVersion(options.promptVersion)
8670
+ });
8436
8671
  });
8437
8672
  const auth = configureCommand(admin.command("auth").description("Authentication commands"));
8438
8673
  auth.command("spotify").description("Authorize Spotify access").action(async () => {
@@ -8564,7 +8799,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
8564
8799
  console.log(` mime: ${result.mime}`);
8565
8800
  }
8566
8801
  async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
8567
- const script = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
8802
+ const script = options.scriptFile ? readFileSync7(options.scriptFile, "utf8") : options.script;
8568
8803
  if (!idOrLogId || !script || !script.trim()) {
8569
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]");
8570
8805
  }
@@ -8581,6 +8816,7 @@ async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
8581
8816
  durationMs,
8582
8817
  durationTargetSec,
8583
8818
  force: options.force,
8819
+ promptVersion: parsePromptVersion(options.promptVersion),
8584
8820
  script: script.trim(),
8585
8821
  voiceId: options.voiceId
8586
8822
  });
@@ -8619,14 +8855,22 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
8619
8855
  console.log(` sources: ${result.sources.join(", ")}`);
8620
8856
  }
8621
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
+ }
8622
8865
  async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
8623
- const note = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
8866
+ const note = options.scriptFile ? readFileSync7(options.scriptFile, "utf8") : options.script;
8624
8867
  if (!idOrLogId || !note || !note.trim()) {
8625
8868
  throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--dry-run] [--json]");
8626
8869
  }
8627
8870
  const result = await trackNoteCommand2(idOrLogId, {
8628
8871
  dryRun: options.dryRun,
8629
- note: note.trim()
8872
+ note: note.trim(),
8873
+ promptVersion: parsePromptVersion(options.promptVersion)
8630
8874
  });
8631
8875
  if (options.json) {
8632
8876
  printJson(result);
@@ -9059,7 +9303,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
9059
9303
  return;
9060
9304
  }
9061
9305
  const candidate = path2.join(dir, name);
9062
- return existsSync6(candidate) ? candidate : undefined;
9306
+ return existsSync7(candidate) ? candidate : undefined;
9063
9307
  };
9064
9308
  const resolveFile = (explicit, name) => {
9065
9309
  if (explicit) {
@@ -9271,7 +9515,7 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand3) {
9271
9515
  if (options.analyzedFrom !== undefined && options.analyzedFrom !== "full" && options.analyzedFrom !== "preview") {
9272
9516
  throw new Error(`Invalid --analyzed-from: ${options.analyzedFrom} (expected full or preview)`);
9273
9517
  }
9274
- const embeddingRaw = options.embeddingFile ? readFileSync6(options.embeddingFile, "utf8") : options.embedding;
9518
+ const embeddingRaw = options.embeddingFile ? readFileSync7(options.embeddingFile, "utf8") : options.embedding;
9275
9519
  const embedding = parseEmbeddingArg(embeddingRaw);
9276
9520
  const result = await trackUpdateCommand3(trackId, {
9277
9521
  analyzedAt: options.analyzedAt,
@@ -9644,6 +9888,7 @@ async function runLogbookWrite(sector, options, writeCommand) {
9644
9888
  const result = await writeCommand(sector, {
9645
9889
  body: options.body,
9646
9890
  bodyFile: options.bodyFile,
9891
+ promptVersion: parsePromptVersion(options.promptVersion),
9647
9892
  title: options.title
9648
9893
  });
9649
9894
  if (options.json) {
@@ -9653,6 +9898,109 @@ async function runLogbookWrite(sector, options, writeCommand) {
9653
9898
  const skipped = "skipped" in result && result.skipped;
9654
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}`);
9655
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
+ }
9656
10004
  async function runAdd(spotifyUrl, options, addCommand3) {
9657
10005
  if (!spotifyUrl) {
9658
10006
  throw new Error("Missing Spotify track URL");
@@ -9858,7 +10206,7 @@ async function runGalaxyEmbeddings(options, galaxyEmbeddingsCommand2) {
9858
10206
  console.log(`${result.embeddings.length} embedding(s)${result.nextCursor ? ` \xB7 nextCursor: ${result.nextCursor}` : " \xB7 end of corpus"}`);
9859
10207
  }
9860
10208
  async function runGalaxyMapWrite(options, galaxyMapWriteCommand2) {
9861
- const raw = JSON.parse(readFileSync6(options.file, "utf8"));
10209
+ const raw = JSON.parse(readFileSync7(options.file, "utf8"));
9862
10210
  const clusters = Array.isArray(raw) || raw === null ? raw : raw.clusters;
9863
10211
  if (!Array.isArray(clusters)) {
9864
10212
  throw new Error("Invalid --file: expected { clusters: [...] } or a JSON array of cluster rows");
@@ -10400,6 +10748,7 @@ function normalizeCommanderError(error) {
10400
10748
  return new Error(message);
10401
10749
  }
10402
10750
  var stringOptions = new Set([
10751
+ "--against",
10403
10752
  "--analyzed-at",
10404
10753
  "--analyzed-from",
10405
10754
  "--at",
@@ -10451,6 +10800,7 @@ var stringOptions = new Set([
10451
10800
  "--plate-background",
10452
10801
  "--platform",
10453
10802
  "--poster",
10803
+ "--prompt-version",
10454
10804
  "--props",
10455
10805
  "--query",
10456
10806
  "--reasoning",
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.135.0"
34
+ "version": "0.136.0"
35
35
  }