@lingjingai/scriptctl 0.9.8 → 0.10.1

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.
@@ -1,13 +1,10 @@
1
- import * as fs from "node:fs";
2
- import * as os from "node:os";
3
1
  import * as path from "node:path";
4
- import { randomUUID } from "node:crypto";
5
- import { CliError, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_RUNTIME, EXIT_USAGE, directDir, exists, readJson, readText, scriptJsonPath, sha256Text, writeJson, } from "../common.js";
2
+ import { CliError, EXIT_INPUT, EXIT_NEEDS_AGENT, EXIT_OK, EXIT_RUNTIME, EXIT_USAGE, directDir, exists, readJson, readText, sha256Text, writeJson, } from "../common.js";
6
3
  import { applyPatchOperations, collectAssetRefs, collectStateRefs, parseAnyAddress, parseStateTarget, validateScript, PATCH_OP_SCHEMA, } from "../domain/script-core.js";
7
4
  import { RemoteScriptOutputStore } from "../infra/script-output-api.js";
8
5
  import { LocalScriptOutputStore } from "../infra/local-script-output-store.js";
9
6
  import { ScriptOutputApiError, resolveOutputMode, } from "../infra/script-output-store.js";
10
- import { markMetadataConfidenceReviewed, readRunState, reviewBlockers, summarizeIssues, updateRunState, markPatched, } from "./direct.js";
7
+ import { readRunState, summarizeIssues, updateRunState, } from "./direct.js";
11
8
  function strOf(v) {
12
9
  if (v === null || v === undefined)
13
10
  return "";
@@ -65,14 +62,12 @@ function episodeCharCounts(scenes) {
65
62
  export class ScriptEditSession {
66
63
  workspace;
67
64
  script;
68
- scriptPath;
69
65
  client;
70
66
  projectGroupNo;
71
67
  revision;
72
68
  constructor(opts) {
73
69
  this.workspace = opts.workspace;
74
70
  this.script = opts.script;
75
- this.scriptPath = opts.scriptPath ?? null;
76
71
  this.client = opts.client ?? null;
77
72
  this.projectGroupNo = opts.projectGroupNo ?? null;
78
73
  this.revision = opts.revision ?? null;
@@ -81,16 +76,10 @@ export class ScriptEditSession {
81
76
  return this.client !== null;
82
77
  }
83
78
  get artifactLabel() {
84
- if (this.remote) {
85
- // Public label strips the internal `db:/script-output/project-groups/`
86
- // storage prefix (which leaks implementation), but keeps the project id
87
- // and revision so two different project groups at the same revision stay
88
- // distinguishable in user-visible report.artifacts entries. The project
89
- // id is not a secret: the user supplies it themselves via
90
- // `--project-group-no` or env config.
91
- return `script-output/${this.projectGroupNo}@revision/${this.revision ?? 0}`;
92
- }
93
- return this.scriptPath ?? "";
79
+ // Public label — keeps the project id + revision (not secret; the user
80
+ // supplies the project id via --project-group-no/env) so two project groups
81
+ // at the same revision stay distinguishable in report.artifacts.
82
+ return `script-output/${this.projectGroupNo}@revision/${this.revision ?? 0}`;
94
83
  }
95
84
  }
96
85
  export function scriptOutputClient(opts) {
@@ -212,7 +201,7 @@ export function apiErrorToCli(title, exc) {
212
201
  isConflict
213
202
  ? "Reload the latest script revision and retry."
214
203
  : isMissing
215
- ? "Run `scriptctl direct export` to publish a script first."
204
+ ? "Run `scriptctl import push` to publish a script first."
216
205
  : "Run `scriptctl doctor` to verify gateway configuration; retry if it was transient.",
217
206
  ],
218
207
  errorCode: isConflict
@@ -236,7 +225,11 @@ export async function currentRevisionOrZero(client) {
236
225
  throw exc;
237
226
  }
238
227
  }
239
- async function loadRemoteScript(opts, workspace) {
228
+ // All reads/edits operate on the DB-backed final script. The intermediate
229
+ // script.initial.json is a private artifact between `import init` and
230
+ // `import push`; it is not an editable surface.
231
+ async function loadScript(opts) {
232
+ const workspace = strOf(opts["workspace_path"] || "workspace");
240
233
  const client = scriptOutputClient(opts);
241
234
  let script;
242
235
  let revision;
@@ -255,7 +248,7 @@ async function loadRemoteScript(opts, workspace) {
255
248
  exitCode: EXIT_INPUT,
256
249
  required: ["existing script-output project document"],
257
250
  received: [`projectGroupNo=${client.projectGroupNo}`],
258
- nextSteps: ["Run `scriptctl direct export` first, or pass `--script-path` to edit a local intermediate script JSON."],
251
+ nextSteps: ["Run `scriptctl import push` to publish a script first."],
259
252
  errorCode: "SCRIPT_NOT_FOUND",
260
253
  });
261
254
  }
@@ -276,66 +269,22 @@ async function loadRemoteScript(opts, workspace) {
276
269
  revision: Number((revision ?? {})["revision"] ?? 0),
277
270
  });
278
271
  }
279
- async function loadScriptForEdit(opts) {
280
- const workspace = strOf(opts["workspace_path"] || "workspace");
281
- if (!opts["script_path"]) {
282
- return loadRemoteScript(opts, workspace);
283
- }
284
- const p = scriptJsonPath(opts);
285
- if (!exists(p)) {
286
- throw new CliError("SCRIPT BLOCKED: Local script file not found", "Local script file not found.", {
287
- exitCode: EXIT_INPUT,
288
- required: ["--script-path existing local script JSON"],
289
- received: [p],
290
- nextSteps: ["Pass --script-path to an existing local intermediate script JSON, or omit --script-path to use DB-backed script-output."],
291
- errorCode: "SCRIPT_NOT_FOUND",
292
- });
293
- }
294
- let data;
295
- try {
296
- data = readJson(p);
297
- }
298
- catch (exc) {
299
- throw new CliError("SCRIPT BLOCKED: script JSON invalid", "script JSON invalid.", {
300
- exitCode: EXIT_INPUT,
301
- required: ["valid JSON"],
302
- received: [`${p}: ${exc.message}`],
303
- nextSteps: ["Fix the local script JSON syntax before editing."],
304
- errorCode: "SCRIPT_JSON_INVALID",
305
- });
306
- }
307
- if (!isDict(data)) {
308
- throw new CliError("SCRIPT BLOCKED: script root invalid", "script root invalid.", {
309
- exitCode: EXIT_USAGE,
310
- required: ["script root object"],
311
- received: [Array.isArray(data) ? "array" : typeof data],
312
- nextSteps: ["Use a valid script document object."],
313
- errorCode: "SCRIPT_ROOT_INVALID",
314
- });
315
- }
316
- return new ScriptEditSession({ workspace, scriptPath: p, script: data });
317
- }
318
272
  function validateSession(session, opts = {}) {
319
- const requireSource = opts.requireSource ?? false;
320
- if (!session.remote) {
321
- return validateScript(session.workspace, session.scriptPath, { requireSource });
322
- }
323
- const tmpPath = path.join(os.tmpdir(), `scriptctl-db-script-${randomUUID()}.json`);
324
- try {
325
- fs.writeFileSync(tmpPath, JSON.stringify(session.script, null, 2) + "\n", "utf-8");
326
- const validation = validateScript(session.workspace, tmpPath, { requireSource });
327
- validation["script_path"] = session.artifactLabel;
273
+ const persist = opts.persist ?? true;
274
+ // Validate the in-memory DB script directly (no tmp file). scriptData also
275
+ // tells validateScript not to read the local asset_metadata.json/episode_plan.json
276
+ // parse artifacts — those describe the local initial.json, not the DB script.
277
+ // requireSource:false (DB validation never needs source.txt). persist:false so
278
+ // validateScript doesn't write; we own the write below to set the nicer label.
279
+ const validation = validateScript(session.workspace, null, {
280
+ requireSource: false,
281
+ scriptData: session.script,
282
+ persist: false,
283
+ });
284
+ validation["script_path"] = session.artifactLabel;
285
+ if (persist)
328
286
  writeJson(path.join(directDir(session.workspace), "validation.json"), validation);
329
- return validation;
330
- }
331
- finally {
332
- try {
333
- fs.unlinkSync(tmpPath);
334
- }
335
- catch {
336
- // ignore
337
- }
338
- }
287
+ return validation;
339
288
  }
340
289
  function validationIssuePath(issue) {
341
290
  if (issue["path"])
@@ -425,12 +374,8 @@ function requestIdForScriptWrite(opts, op, payload) {
425
374
  return `scriptctl:${op}:${sha256Text(canonical)}`;
426
375
  }
427
376
  async function saveScriptSession(session, opts, op) {
428
- if (!session.remote) {
429
- writeJson(session.scriptPath, session.script);
430
- return [null, false];
431
- }
432
377
  if (session.client === null)
433
- throw new Error("remote script session missing client");
378
+ throw new Error("script session missing client");
434
379
  const baseRevision = Number(session.revision ?? 0);
435
380
  const requestId = requestIdForScriptWrite(opts, op, { script: session.script });
436
381
  let res;
@@ -488,67 +433,11 @@ function patchOperationsFromPayload(payload) {
488
433
  return operations;
489
434
  }
490
435
  // ---------------------------------------------------------------------------
491
- // command_patch (legacy: applies to script.initial.json directly with source)
492
- // ---------------------------------------------------------------------------
493
- export function commandPatch(opts) {
494
- const workspace = strOf(opts["workspace_path"] || "workspace");
495
- const patchPath = strOf(opts["patch"]);
496
- const scriptPath = path.join(directDir(workspace), "script.initial.json");
497
- const sourcePath = path.join(workspace, "source.txt");
498
- if (!exists(patchPath)) {
499
- throw new CliError("PATCH BLOCKED: Patch file not found", "Patch file not found.", {
500
- exitCode: EXIT_INPUT,
501
- required: ["--patch: existing JSON file"],
502
- received: [patchPath],
503
- nextSteps: ["Write a patch JSON file and rerun patch."],
504
- });
505
- }
506
- if (!exists(scriptPath) || !exists(sourcePath)) {
507
- throw new CliError("PATCH BLOCKED: Required artifact missing", "Required artifact missing.", {
508
- exitCode: EXIT_INPUT,
509
- required: ["source.txt and script.initial.json"],
510
- received: [scriptPath, sourcePath],
511
- nextSteps: ["Run scriptctl direct init first."],
512
- });
513
- }
514
- let payload;
515
- try {
516
- payload = readJson(patchPath);
517
- }
518
- catch (exc) {
519
- throw new CliError("PATCH BLOCKED: Patch JSON invalid", "Patch JSON invalid.", {
520
- exitCode: EXIT_USAGE,
521
- required: ["valid JSON patch file"],
522
- received: [`${patchPath}: ${exc.message}`],
523
- nextSteps: ["Fix patch JSON and rerun patch."],
524
- });
525
- }
526
- const operations = patchOperationsFromPayload(payload);
527
- const script = readJson(scriptPath);
528
- const applied = applyPatchOperations(script, readText(sourcePath), operations);
529
- writeJson(scriptPath, script);
530
- markMetadataConfidenceReviewed(workspace, operations);
531
- const validation = validateScript(workspace, scriptPath);
532
- markPatched(workspace, applied.length);
533
- const passed = Boolean(validation["passed"]);
534
- const report = {
535
- title: passed ? "PATCH APPLIED: Validation passed" : "PATCH APPLIED: Repair issues remain",
536
- result: [
537
- `operations: ${applied.length}`,
538
- `validation: ${passed ? "passed" : "needs repair"}`,
539
- ],
540
- artifacts: [scriptPath, path.join(directDir(workspace), "validation.json")],
541
- issues: summarizeIssues(asList(validation["issues"])),
542
- next: [passed ? "Export the final script." : "Inspect remaining issues and apply another patch."],
543
- };
544
- return [report, passed ? EXIT_OK : EXIT_NEEDS_AGENT];
545
- }
546
- // ---------------------------------------------------------------------------
547
436
  // commandScriptValidate / commandScriptInspect
548
437
  // ---------------------------------------------------------------------------
549
438
  export async function commandScriptValidate(opts) {
550
439
  const workspace = strOf(opts["workspace_path"] || "workspace");
551
- const session = await loadScriptForEdit(opts);
440
+ const session = await loadScript(opts);
552
441
  const validation = validateSession(session);
553
442
  await syncValidationResult(session, validation);
554
443
  const stats = validation["stats"] ?? {};
@@ -678,13 +567,13 @@ export async function commandScriptPatch(opts) {
678
567
  }
679
568
  const operations = patchOperationsFromPayload(payload);
680
569
  const dryRun = Boolean(opts["dry_run"]);
681
- const session = await loadScriptForEdit(opts);
570
+ const session = await loadScript(opts);
682
571
  const script = session.script;
683
572
  const applied = applyPatchOperations(script, scriptSourceText(workspace), operations);
684
573
  if (dryRun) {
685
574
  // Validate the in-memory mutated script WITHOUT calling saveScriptSession.
686
575
  // Run validate but skip syncValidationResult (which writes to remote) too.
687
- const validation = validateSession(session);
576
+ const validation = validateSession(session, { persist: false });
688
577
  const passed = Boolean(validation["passed"]);
689
578
  const report = {
690
579
  title: passed ? "SCRIPT PATCH DRY-RUN PASSED" : "SCRIPT PATCH DRY-RUN: Validation needs repair",
@@ -706,12 +595,6 @@ export async function commandScriptPatch(opts) {
706
595
  const [newRevision, idempotent] = await saveScriptSession(session, opts, applied.length === 1 ? applied[0] : "script.patch");
707
596
  const validation = validateSession(session);
708
597
  await syncValidationResult(session, validation, newRevision);
709
- // Direct-stage local patches: keep run_state in sync so reviewBlockers can
710
- // unblock direct export. Detect via script path under directDir.
711
- if (!session.remote && session.scriptPath && session.scriptPath.startsWith(directDir(workspace))) {
712
- markMetadataConfidenceReviewed(workspace, operations);
713
- markPatched(workspace, applied.length);
714
- }
715
598
  const passed = Boolean(validation["passed"]);
716
599
  const resultLines = [
717
600
  `operations: ${applied.length}`,
@@ -739,7 +622,7 @@ export async function commandScriptPatch(opts) {
739
622
  // ---------------------------------------------------------------------------
740
623
  async function applySingleScriptOp(opts, op) {
741
624
  const workspace = strOf(opts["workspace_path"] || "workspace");
742
- const session = await loadScriptForEdit(opts);
625
+ const session = await loadScript(opts);
743
626
  const script = session.script;
744
627
  const applied = applyPatchOperations(script, scriptSourceText(workspace), [op]);
745
628
  const opName = applied[0] ?? strOf(op["op"]);
@@ -781,7 +664,7 @@ function summarizeScriptOp(_script, op) {
781
664
  return `已执行 ${kind}`;
782
665
  }
783
666
  async function commandStateRefs(opts, plan = false) {
784
- const session = await loadScriptForEdit(opts);
667
+ const session = await loadScript(opts);
785
668
  const script = session.script;
786
669
  const args = asList(opts["_args"]);
787
670
  const [targetKind, targetId, stateId] = parseStateTarget(args[0] ?? "");
@@ -820,72 +703,48 @@ async function commandStateRefs(opts, plan = false) {
820
703
  return [report, EXIT_OK];
821
704
  }
822
705
  // ---------------------------------------------------------------------------
823
- // command_export (direct export)
706
+ // command_push (import push)
824
707
  // ---------------------------------------------------------------------------
825
- export async function commandExport(opts) {
708
+ export async function commandPush(opts) {
826
709
  const workspace = strOf(opts["workspace_path"] || "workspace");
827
- const force = Boolean(opts["force"]);
828
710
  const scriptPath = path.join(directDir(workspace), "script.initial.json");
829
711
  if (!exists(scriptPath)) {
830
- throw new CliError("EXPORT BLOCKED: script.initial.json not found", "script.initial.json not found.", {
712
+ throw new CliError("PUSH BLOCKED: script.initial.json not found", "script.initial.json not found.", {
831
713
  exitCode: EXIT_INPUT,
832
714
  required: ["workspace/draft/scriptctl/direct/script.initial.json"],
833
715
  received: [scriptPath],
834
- nextSteps: ["Run scriptctl direct init first."],
716
+ nextSteps: ["Run scriptctl import init first."],
835
717
  });
836
718
  }
837
719
  const state = readRunState(workspace);
838
720
  const provider = strOf(state["provider"]);
839
- if (provider === "mock" && process.env.SCRIPTCTL_ALLOW_MOCK_EXPORT !== "1") {
721
+ // SCRIPTCTL_ALLOW_MOCK_PUSH is the current name; SCRIPTCTL_ALLOW_MOCK_EXPORT is
722
+ // still honored for back-compat with environments configured before the rename.
723
+ const allowMock = process.env.SCRIPTCTL_ALLOW_MOCK_PUSH === "1" || process.env.SCRIPTCTL_ALLOW_MOCK_EXPORT === "1";
724
+ if (provider === "mock" && !allowMock) {
840
725
  const report = {
841
- title: "EXPORT BLOCKED: Mock provider result",
842
- result: ["script.initial.json was produced by --provider mock and was not exported."],
726
+ title: "PUSH BLOCKED: Mock provider result",
727
+ result: ["script.initial.json was produced by --provider mock and was not pushed."],
843
728
  artifacts: [path.join(directDir(workspace), "run_state.json")],
844
729
  next: ["Rerun init with --provider anthropic for deliverable conversion."],
845
730
  };
846
731
  return [report, EXIT_NEEDS_AGENT];
847
732
  }
848
- const missingReview = reviewBlockers(state);
849
- if (missingReview.length > 0) {
850
- const report = {
851
- title: "EXPORT BLOCKED: Agent review incomplete",
852
- result: ["script.initial.json was not exported.", `missing review: ${missingReview.join(", ")}`],
853
- artifacts: [path.join(directDir(workspace), "run_state.json")],
854
- next: ["Run inspect for each missing target, or apply a structured patch, then validate/export."],
855
- };
856
- return [report, EXIT_NEEDS_AGENT];
857
- }
858
- const validation = validateScript(workspace, scriptPath);
859
- const blockingOrError = Boolean(validation["has_blocking"]) ||
860
- asList(validation["issues"]).some((it) => isDict(it) && (it["severity"] === "blocking" || it["severity"] === "error"));
861
- if (!validation["passed"] && (!force || blockingOrError)) {
862
- const title = force
863
- ? "EXPORT BLOCKED: Validation errors require repair"
864
- : "EXPORT BLOCKED: Validation needs agent repair";
865
- const report = {
866
- title,
867
- result: ["script.initial.json was not exported."],
868
- artifacts: [path.join(directDir(workspace), "validation.json")],
869
- issues: summarizeIssues(asList(validation["issues"])),
870
- next: ["Inspect issues and apply structured patches, then validate/export."],
871
- };
872
- return [report, EXIT_NEEDS_AGENT];
873
- }
874
733
  let script;
875
734
  try {
876
735
  script = readJson(scriptPath);
877
736
  }
878
737
  catch (exc) {
879
- throw new CliError("EXPORT BLOCKED: script.initial.json invalid", "script.initial.json invalid.", {
738
+ throw new CliError("PUSH BLOCKED: script.initial.json invalid", "script.initial.json invalid.", {
880
739
  exitCode: EXIT_INPUT,
881
740
  required: ["valid script.initial.json"],
882
741
  received: [`${scriptPath}: ${exc.message}`],
883
- nextSteps: ["Fix script.initial.json or rerun direct init."],
742
+ nextSteps: ["Fix script.initial.json or rerun import init."],
884
743
  errorCode: "SCRIPT_JSON_INVALID",
885
744
  });
886
745
  }
887
746
  if (!isDict(script)) {
888
- throw new CliError("EXPORT BLOCKED: script root invalid", "script root invalid.", {
747
+ throw new CliError("PUSH BLOCKED: script root invalid", "script root invalid.", {
889
748
  exitCode: EXIT_USAGE,
890
749
  required: ["script root object"],
891
750
  received: [Array.isArray(script) ? "array" : typeof script],
@@ -893,12 +752,14 @@ export async function commandExport(opts) {
893
752
  errorCode: "SCRIPT_ROOT_INVALID",
894
753
  });
895
754
  }
755
+ // Validation is advisory, not a gate: push always publishes so the agent can
756
+ // self-review and repair on the DB script afterwards. requireSource:false so a
757
+ // missing source.txt never aborts the publish. Results are still computed and
758
+ // synced so the review subagent has the issue list to act on.
759
+ const validation = validateScript(workspace, scriptPath, { requireSource: false });
896
760
  const client = scriptOutputClient(opts);
897
761
  const baseRevision = await currentRevisionOrZero(client);
898
- // Sorted-keys serialization mirrors Python json.dumps(sort_keys=True, separators=(",",":"))
899
- const sortedScript = sortDeep(script);
900
- const scriptHash = sha256Text(JSON.stringify(sortedScript));
901
- const requestId = strOf(opts["request_id"]).trim() || `scriptctl-direct-export:${scriptHash}`;
762
+ const requestId = requestIdForScriptWrite(opts, "import.push", { script });
902
763
  let replaceRes;
903
764
  try {
904
765
  replaceRes = await client.replaceScript({
@@ -910,7 +771,7 @@ export async function commandExport(opts) {
910
771
  }
911
772
  catch (exc) {
912
773
  if (exc instanceof ScriptOutputApiError) {
913
- throw apiErrorToCli("SCRIPT API BLOCKED: Export write failed", exc);
774
+ throw apiErrorToCli("SCRIPT API BLOCKED: Push write failed", exc);
914
775
  }
915
776
  throw exc;
916
777
  }
@@ -924,17 +785,21 @@ export async function commandExport(opts) {
924
785
  });
925
786
  await syncValidationResult(remoteSession, validation, revision);
926
787
  const outputLabel = remoteSession.artifactLabel;
927
- updateRunState(workspace, { status: "exported", output_path: outputLabel });
788
+ updateRunState(workspace, { status: "pushed", output_path: outputLabel });
789
+ const passed = Boolean(validation["passed"]);
928
790
  const report = {
929
- title: "EXPORT COMPLETE: Final script stored in DB",
791
+ title: "PUSH COMPLETE: Final script stored in DB",
930
792
  result: [
931
- `validation: ${validation["passed"] ? "passed" : "forced"}`,
793
+ `validation: ${passed ? "passed" : "advisory (review on DB)"}`,
932
794
  `base_revision: ${baseRevision}`,
933
795
  `revision: ${revision}`,
934
796
  `idempotent: ${String(Boolean(replaceRes["idempotent"])).toLowerCase()}`,
935
797
  ],
798
+ issues: passed ? [] : summarizeIssues(asList(validation["issues"])),
936
799
  artifacts: [outputLabel, path.join(directDir(workspace), "validation.json")],
937
- next: ["Proceed to downstream asset or footage stages."],
800
+ next: passed
801
+ ? ["Self-review the DB script, then proceed to downstream asset or footage stages."]
802
+ : ["Self-review and repair the DB script (validate / issues --severity error), then proceed downstream."],
938
803
  };
939
804
  return [report, EXIT_OK];
940
805
  }
@@ -1081,7 +946,7 @@ function buildReport(op, title, lines, artifactLabel, next) {
1081
946
  }
1082
947
  // ----- summary --------------------------------------------------------------
1083
948
  export async function commandSummary(opts) {
1084
- const session = await loadScriptForEdit(opts);
949
+ const session = await loadScript(opts);
1085
950
  const script = session.script;
1086
951
  const episodes = asList(script["episodes"]);
1087
952
  const scenes = [];
@@ -1104,7 +969,7 @@ export async function commandSummary(opts) {
1104
969
  }
1105
970
  // ----- episodes -------------------------------------------------------------
1106
971
  export async function commandEpisodes(opts) {
1107
- const session = await loadScriptForEdit(opts);
972
+ const session = await loadScript(opts);
1108
973
  const script = session.script;
1109
974
  const itemId = strOf(opts["id"]).trim();
1110
975
  const minChars = parseBound(opts["min_chars"]);
@@ -1158,7 +1023,7 @@ function formatScene(epId, scene, names) {
1158
1023
  return `${epId}/${scene["scene_id"]} [${space} ${time}] location=${locations.join(",") || "-"} actors=${actors.join(",") || "-"} props=${props.join(",") || "-"} actions=${actionCount}`;
1159
1024
  }
1160
1025
  export async function commandScenes(opts) {
1161
- const session = await loadScriptForEdit(opts);
1026
+ const session = await loadScript(opts);
1162
1027
  const script = session.script;
1163
1028
  const inFilter = parseInFilter(strOf(opts["in"]));
1164
1029
  const hasActor = strOf(opts["has_actor"]).trim();
@@ -1196,7 +1061,7 @@ export async function commandScenes(opts) {
1196
1061
  }
1197
1062
  // ----- actions --------------------------------------------------------------
1198
1063
  export async function commandActions(opts) {
1199
- const session = await loadScriptForEdit(opts);
1064
+ const session = await loadScript(opts);
1200
1065
  const script = session.script;
1201
1066
  const inFilter = parseInFilter(strOf(opts["in"]));
1202
1067
  const grep = strOf(opts["grep"]);
@@ -1381,7 +1246,7 @@ function listAssetsByKind(script, kind, opts) {
1381
1246
  return lines;
1382
1247
  }
1383
1248
  export async function commandActors(opts) {
1384
- const session = await loadScriptForEdit(opts);
1249
+ const session = await loadScript(opts);
1385
1250
  const lines = listAssetsByKind(session.script, "actor", {
1386
1251
  id: strOf(opts["id"]).trim(),
1387
1252
  name: strOf(opts["name"]).trim(),
@@ -1390,7 +1255,7 @@ export async function commandActors(opts) {
1390
1255
  return [buildReport("query.actors", "ACTORS", lines, session.artifactLabel, ["Use `scriptctl rename actor:<id>` / `describe` / `merge` to edit."]), EXIT_OK];
1391
1256
  }
1392
1257
  export async function commandLocations(opts) {
1393
- const session = await loadScriptForEdit(opts);
1258
+ const session = await loadScript(opts);
1394
1259
  const lines = listAssetsByKind(session.script, "location", {
1395
1260
  id: strOf(opts["id"]).trim(),
1396
1261
  name: strOf(opts["name"]).trim(),
@@ -1399,7 +1264,7 @@ export async function commandLocations(opts) {
1399
1264
  return [buildReport("query.locations", "LOCATIONS", lines, session.artifactLabel, ["Use `scriptctl rename location:<id>` etc. to edit."]), EXIT_OK];
1400
1265
  }
1401
1266
  export async function commandProps(opts) {
1402
- const session = await loadScriptForEdit(opts);
1267
+ const session = await loadScript(opts);
1403
1268
  const lines = listAssetsByKind(session.script, "prop", {
1404
1269
  id: strOf(opts["id"]).trim(),
1405
1270
  name: strOf(opts["name"]).trim(),
@@ -1408,7 +1273,7 @@ export async function commandProps(opts) {
1408
1273
  return [buildReport("query.props", "PROPS", lines, session.artifactLabel, ["Use `scriptctl rename prop:<id>` etc. to edit."]), EXIT_OK];
1409
1274
  }
1410
1275
  export async function commandAssets(opts) {
1411
- const session = await loadScriptForEdit(opts);
1276
+ const session = await loadScript(opts);
1412
1277
  const kindFilter = strOf(opts["kind"]).trim();
1413
1278
  const nameOpt = strOf(opts["name"]).trim();
1414
1279
  const idOpt = strOf(opts["id"]).trim();
@@ -1423,7 +1288,7 @@ export async function commandAssets(opts) {
1423
1288
  }
1424
1289
  // ----- speakers -------------------------------------------------------------
1425
1290
  export async function commandSpeakers(opts) {
1426
- const session = await loadScriptForEdit(opts);
1291
+ const session = await loadScript(opts);
1427
1292
  const script = session.script;
1428
1293
  const idOpt = strOf(opts["id"]).trim();
1429
1294
  const nameOpt = strOf(opts["name"]).trim();
@@ -1446,7 +1311,7 @@ export async function commandSpeakers(opts) {
1446
1311
  }
1447
1312
  // ----- issues ---------------------------------------------------------------
1448
1313
  export async function commandIssues(opts) {
1449
- const session = await loadScriptForEdit(opts);
1314
+ const session = await loadScript(opts);
1450
1315
  const severityFilter = strOf(opts["severity"]).trim();
1451
1316
  const codeFilter = strOf(opts["code"]).trim();
1452
1317
  const validation = validateSession(session);
@@ -1468,7 +1333,7 @@ export async function commandIssues(opts) {
1468
1333
  }
1469
1334
  // ----- refs (unified reverse lookup) ----------------------------------------
1470
1335
  export async function commandRefs(opts) {
1471
- const session = await loadScriptForEdit(opts);
1336
+ const session = await loadScript(opts);
1472
1337
  const script = session.script;
1473
1338
  const args = asList(opts["_args"]);
1474
1339
  const target = strOf(args[0]).trim();