@rogatio/cli 5.13.2 → 5.14.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.
package/README.md CHANGED
@@ -40,7 +40,7 @@ rogatio <command> [options]
40
40
  | `rogatio verify [path]` | Validate a `.rogatio.json` file (schema + compiler). |
41
41
  | `rogatio test [path] [url...]` | Run offline dry-run tests against `.rogatio.json`. |
42
42
  | `rogatio ai <setup\|ls\|show\|delete\|test>` | AI provider configuration. `setup` interactive; `ls` list; `show` redacted; `delete` remove; `test` connection. |
43
- | `rogatio runtime <install\|uninstall>` | Register the native-messaging host (and, on capable platforms, the device-local CA) in one transactional install; `uninstall` removes the host manifest, the device-local CA files, and the CA trust installation (idempotent). |
43
+ | `rogatio runtime <install\|uninstall>` | Register the native-messaging host with `install --extension-id <id>` (and, on capable platforms, the device-local CA) in one transactional install; `uninstall` removes the host manifest, the device-local CA files, and the CA trust installation (idempotent). |
44
44
  | `rogatio runtime host <path>` | Run the consolidated native-messaging host for the project (pair/authorize/body transforms over stdio). Normally launched by the browser; run manually only for debugging. |
45
45
 
46
46
  Global options: `--help, -h` and `--version, -v`. Run `rogatio <command> --help`
@@ -2050,9 +2050,10 @@ var EditorControllerImpl = class {
2050
2050
  this.instanceId = `rogatio-editor-${++editorInstanceCount}`;
2051
2051
  this.draft = initial;
2052
2052
  this.committed = cloneSnapshot(initial);
2053
- const initialDiagnostics = this.collectDiagnostics(this.draft);
2054
- if (initialDiagnostics.length > 0) {
2055
- throw new EditorInitializationError(initialDiagnostics);
2053
+ this.errors = this.collectDiagnostics(this.draft);
2054
+ if (this.errors.length > 0) {
2055
+ this.statusMessage = `${this.errors.length} validation error${this.errors.length === 1 ? "" : "s"} found.`;
2056
+ this.focusRequest = this.errors[0]?.path;
2056
2057
  }
2057
2058
  if (options.aiAssist) {
2058
2059
  this.aiAssistPanel = createAIAssistPanel(
@@ -2341,12 +2342,23 @@ var EditorControllerImpl = class {
2341
2342
  if (groupId) this.requestRemoveGroup(groupId);
2342
2343
  return;
2343
2344
  }
2345
+ if (command === "copy-group") {
2346
+ const groupId = element.dataset.groupId;
2347
+ if (groupId) this.copyGroup(groupId);
2348
+ return;
2349
+ }
2344
2350
  if (command === "remove-rule") {
2345
2351
  const groupId = element.dataset.groupId;
2346
2352
  const ruleId = element.dataset.ruleId;
2347
2353
  if (groupId && ruleId) this.requestRemoveRule(groupId, ruleId);
2348
2354
  return;
2349
2355
  }
2356
+ if (command === "copy-rule") {
2357
+ const groupId = element.dataset.groupId;
2358
+ const ruleId = element.dataset.ruleId;
2359
+ if (groupId && ruleId) this.copyRule(groupId, ruleId);
2360
+ return;
2361
+ }
2350
2362
  if (command === "add-group-origin") {
2351
2363
  const groupId = element.dataset.groupId;
2352
2364
  if (groupId) this.addGroupOrigin(groupId);
@@ -2604,13 +2616,24 @@ var EditorControllerImpl = class {
2604
2616
  }
2605
2617
  return ids;
2606
2618
  }
2607
- nextId(prefix) {
2608
- const ids = this.allIds();
2619
+ allocateId(prefix, reserved) {
2609
2620
  let candidate = prefix;
2610
2621
  let suffix = 2;
2611
- while (ids.has(candidate)) candidate = `${prefix}-${suffix++}`;
2622
+ while (reserved.has(candidate)) candidate = `${prefix}-${suffix++}`;
2623
+ reserved.add(candidate);
2612
2624
  return candidate;
2613
2625
  }
2626
+ nextId(prefix) {
2627
+ return this.allocateId(prefix, this.allIds());
2628
+ }
2629
+ copyLabel(name) {
2630
+ const base = typeof name === "string" ? name : "";
2631
+ const suffix = " (copy)";
2632
+ if (base.length + suffix.length <= LIMITS.maxLabelLength) {
2633
+ return `${base}${suffix}`;
2634
+ }
2635
+ return base;
2636
+ }
2614
2637
  addGroup() {
2615
2638
  if (this.saving) return;
2616
2639
  const groupId = this.nextId("group-new");
@@ -2652,6 +2675,65 @@ var EditorControllerImpl = class {
2652
2675
  this.statusMessage = "Rule added.";
2653
2676
  this.render();
2654
2677
  }
2678
+ copyRule(groupId, ruleId) {
2679
+ if (this.saving) return;
2680
+ const group = this.groupById(groupId);
2681
+ if (!group) return;
2682
+ const sourceIndex = this.ruleIndex(group, ruleId);
2683
+ if (sourceIndex < 0) return;
2684
+ const snapshot = snapshotOwnData(group.rules[sourceIndex]);
2685
+ if (!snapshot.valid || typeof snapshot.value !== "object" || snapshot.value === null) {
2686
+ this.statusMessage = "Could not copy rule.";
2687
+ this.render();
2688
+ return;
2689
+ }
2690
+ const copied = snapshot.value;
2691
+ copied.id = this.nextId("rule-new");
2692
+ copied.name = this.copyLabel(group.rules[sourceIndex].name);
2693
+ group.rules.splice(sourceIndex + 1, 0, copied);
2694
+ this.markChanged();
2695
+ this.route = { kind: "group", groupId };
2696
+ const groupIndex = this.groupIndex(groupId);
2697
+ this.focusRequest = pointer(
2698
+ "groups",
2699
+ groupIndex,
2700
+ "rules",
2701
+ sourceIndex + 1,
2702
+ "name"
2703
+ );
2704
+ this.statusMessage = "Rule copied.";
2705
+ this.render();
2706
+ }
2707
+ copyGroup(groupId) {
2708
+ if (this.saving) return;
2709
+ const sourceIndex = this.groupIndex(groupId);
2710
+ if (sourceIndex < 0) return;
2711
+ const source = this.draft.groups[sourceIndex];
2712
+ const snapshot = snapshotOwnData(source);
2713
+ if (!snapshot.valid || typeof snapshot.value !== "object" || snapshot.value === null) {
2714
+ this.statusMessage = "Could not copy group.";
2715
+ this.render();
2716
+ return;
2717
+ }
2718
+ const copied = snapshot.value;
2719
+ if (!Array.isArray(copied.rules)) {
2720
+ this.statusMessage = "Could not copy group.";
2721
+ this.render();
2722
+ return;
2723
+ }
2724
+ const reserved = this.allIds();
2725
+ copied.id = this.allocateId("group-new", reserved);
2726
+ copied.name = this.copyLabel(source.name);
2727
+ for (const rule of copied.rules) {
2728
+ rule.id = this.allocateId("rule-new", reserved);
2729
+ }
2730
+ this.draft.groups.splice(sourceIndex + 1, 0, copied);
2731
+ this.markChanged();
2732
+ this.route = { kind: "group", groupId: String(copied.id) };
2733
+ this.focusRequest = pointer("groups", sourceIndex + 1, "name");
2734
+ this.statusMessage = "Group copied.";
2735
+ this.render();
2736
+ }
2655
2737
  moveGroup(groupId, delta) {
2656
2738
  if (this.saving) return;
2657
2739
  const index = this.groupIndex(groupId);
@@ -3367,6 +3449,13 @@ var EditorControllerImpl = class {
3367
3449
  open.dataset.route = "group";
3368
3450
  open.dataset.groupId = groupId;
3369
3451
  open.dataset.btn = "secondary";
3452
+ const copy = this.createCommandButton(
3453
+ "Copy group",
3454
+ "copy-group",
3455
+ this.saving,
3456
+ { groupId }
3457
+ );
3458
+ copy.setAttribute("aria-label", `Copy group ${groupName}`);
3370
3459
  const remove = this.createCommandButton(
3371
3460
  "Remove group",
3372
3461
  "remove-group",
@@ -3375,7 +3464,7 @@ var EditorControllerImpl = class {
3375
3464
  "danger"
3376
3465
  );
3377
3466
  remove.setAttribute("aria-label", `Remove group ${groupName}`);
3378
- item.append(open, remove);
3467
+ item.append(open, copy, remove);
3379
3468
  list.append(item);
3380
3469
  }
3381
3470
  groups.append(list);
@@ -3391,6 +3480,12 @@ var EditorControllerImpl = class {
3391
3480
  headingRow.dataset.groupHeading = "true";
3392
3481
  const heading = this.document.createElement("h2");
3393
3482
  heading.textContent = groupName;
3483
+ const copyGroup = this.createCommandButton(
3484
+ "Copy group",
3485
+ "copy-group",
3486
+ this.saving,
3487
+ { groupId }
3488
+ );
3394
3489
  const removeGroup = this.createCommandButton(
3395
3490
  "Remove group",
3396
3491
  "remove-group",
@@ -3398,7 +3493,7 @@ var EditorControllerImpl = class {
3398
3493
  { groupId },
3399
3494
  "danger"
3400
3495
  );
3401
- headingRow.append(heading, removeGroup);
3496
+ headingRow.append(heading, copyGroup, removeGroup);
3402
3497
  this.form.append(headingRow);
3403
3498
  const settings = this.document.createElement("fieldset");
3404
3499
  settings.dataset.groupCard = "true";
@@ -3608,6 +3703,10 @@ var EditorControllerImpl = class {
3608
3703
  this.saving || ruleIndex >= group.rules.length - 1,
3609
3704
  { groupId, ruleId }
3610
3705
  ),
3706
+ this.createCommandButton("Copy rule", "copy-rule", this.saving, {
3707
+ groupId,
3708
+ ruleId
3709
+ }),
3611
3710
  this.createCommandButton(
3612
3711
  "Remove rule",
3613
3712
  "remove-rule",
@@ -24163,7 +24163,71 @@ var ProjectFileError = class extends ProjectStorageError {
24163
24163
  }
24164
24164
  };
24165
24165
  function emptyProjectDocument() {
24166
- return { version: 1, name: "", groups: [] };
24166
+ return { version: 1, name: randomCivilizationProjectName(), groups: [] };
24167
+ }
24168
+ var PROJECT_NAME_LEADS = Object.freeze([
24169
+ "Apollo",
24170
+ "Atlas",
24171
+ "Aurora",
24172
+ "Beacon",
24173
+ "Chronos",
24174
+ "Cosmos",
24175
+ "Dawn",
24176
+ "Epoch",
24177
+ "Genesis",
24178
+ "Helios",
24179
+ "Horizon",
24180
+ "Lumen",
24181
+ "Meridian",
24182
+ "Nova",
24183
+ "Olympus",
24184
+ "Orion",
24185
+ "Pax",
24186
+ "Prometheus",
24187
+ "Quantum",
24188
+ "Solaris",
24189
+ "Summit",
24190
+ "Terra",
24191
+ "Titan",
24192
+ "Verdant",
24193
+ "Zenith"
24194
+ ]);
24195
+ var PROJECT_NAME_TAILS = Object.freeze([
24196
+ "Accord",
24197
+ "Archive",
24198
+ "Ascension",
24199
+ "Bridge",
24200
+ "Charter",
24201
+ "Circuit",
24202
+ "Codex",
24203
+ "Continuum",
24204
+ "Covenant",
24205
+ "Engine",
24206
+ "Fabric",
24207
+ "Foundation",
24208
+ "Grid",
24209
+ "Lattice",
24210
+ "Legacy",
24211
+ "Mandate",
24212
+ "Nexus",
24213
+ "Orbit",
24214
+ "Protocol",
24215
+ "Relay",
24216
+ "Renaissance",
24217
+ "Spire",
24218
+ "Threshold",
24219
+ "Vault",
24220
+ "Weave"
24221
+ ]);
24222
+ function randomCivilizationProjectName() {
24223
+ const bytes = randomBytes4(2);
24224
+ const lead = pickWord(PROJECT_NAME_LEADS, bytes[0] ?? 0);
24225
+ const tail = pickWord(PROJECT_NAME_TAILS, bytes[1] ?? 0);
24226
+ return `${lead} ${tail}`;
24227
+ }
24228
+ function pickWord(words, byte) {
24229
+ const word = words[byte % words.length];
24230
+ return word ?? words[0] ?? "Nova";
24167
24231
  }
24168
24232
  function isRogatioProjectFilename(name) {
24169
24233
  return name === ".rogatio.json" || name.endsWith(".rogatio.json");
@@ -24443,7 +24507,7 @@ async function editCommand(args, options = {}) {
24443
24507
  if (e instanceof ProjectStorageError && e.code === "not-found") {
24444
24508
  try {
24445
24509
  await storage.create({ id: filePath });
24446
- projectData = emptyProjectDocument();
24510
+ projectData = await storage.get(filePath);
24447
24511
  } catch (createError) {
24448
24512
  console.error(`Error writing initial project: ${createError}`);
24449
24513
  return { exitCode: Promise.resolve(2), shutdown: () => {
@@ -24455,6 +24519,17 @@ async function editCommand(args, options = {}) {
24455
24519
  } };
24456
24520
  }
24457
24521
  }
24522
+ try {
24523
+ projectData = await ensureCivilizationProjectName(
24524
+ storage,
24525
+ filePath,
24526
+ projectData
24527
+ );
24528
+ } catch (e) {
24529
+ console.error(`Error: ${e}`);
24530
+ return { exitCode: Promise.resolve(2), shutdown: () => {
24531
+ } };
24532
+ }
24458
24533
  let aiClient;
24459
24534
  const providerConfig = await readProviderConfig();
24460
24535
  if (providerConfig) {
@@ -24544,6 +24619,26 @@ async function editCommand(args, options = {}) {
24544
24619
  shutdown
24545
24620
  };
24546
24621
  }
24622
+ function needsCivilizationProjectName(data) {
24623
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
24624
+ return false;
24625
+ }
24626
+ if (!Object.hasOwn(data, "name")) return true;
24627
+ const name = data.name;
24628
+ return typeof name !== "string" || name.trim() === "";
24629
+ }
24630
+ async function ensureCivilizationProjectName(storage, filePath, data) {
24631
+ if (!needsCivilizationProjectName(data)) return data;
24632
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
24633
+ return data;
24634
+ }
24635
+ const named = {
24636
+ ...data,
24637
+ name: randomCivilizationProjectName()
24638
+ };
24639
+ await storage.update(filePath, named);
24640
+ return named;
24641
+ }
24547
24642
  function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
24548
24643
  const aiAssistHandler = aiConfigured ? `
24549
24644
  aiAssist: async function*(request) {
@@ -24677,41 +24772,97 @@ function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
24677
24772
 
24678
24773
  const project = await fetchProject();
24679
24774
 
24680
- const editor = createEditor({
24681
- root,
24682
- initialProject: project,
24683
- validate: async (value) => {
24684
- const result = await validateProject(value);
24685
- return result.diagnostics.map((d: any) => ({
24686
- code: d.code,
24687
- severity: d.severity,
24688
- path: d.path,
24689
- message: d.message,
24690
- }));
24691
- },
24692
- save: async (project) => {
24693
- const result = await saveProject(project);
24694
- if (result.ok) {
24695
- return { ok: true };
24775
+ try {
24776
+ const editor = createEditor({
24777
+ root,
24778
+ initialProject: project,
24779
+ // Host validate is sync by contract; use sync XHR on loopback.
24780
+ validate: (value) => {
24781
+ const xhr = new XMLHttpRequest();
24782
+ xhr.open('POST', apiBase + '/api/validate', false);
24783
+ xhr.setRequestHeader('Content-Type', 'application/json');
24784
+ xhr.setRequestHeader('X-CSRF-Token', csrfToken);
24785
+ try {
24786
+ xhr.send(JSON.stringify(value));
24787
+ } catch {
24788
+ return [{
24789
+ code: 'editor.validation-failed',
24790
+ severity: 'error',
24791
+ path: '',
24792
+ message: 'Project validation could not be completed.',
24793
+ }];
24794
+ }
24795
+ if (xhr.status < 200 || xhr.status >= 300) {
24796
+ return [{
24797
+ code: 'editor.validation-failed',
24798
+ severity: 'error',
24799
+ path: '',
24800
+ message: 'Project validation could not be completed.',
24801
+ }];
24802
+ }
24803
+ let result;
24804
+ try {
24805
+ result = JSON.parse(xhr.responseText);
24806
+ } catch {
24807
+ return [{
24808
+ code: 'editor.validation-failed',
24809
+ severity: 'error',
24810
+ path: '',
24811
+ message: 'Project validation could not be completed.',
24812
+ }];
24813
+ }
24814
+ const diagnostics = Array.isArray(result.diagnostics)
24815
+ ? result.diagnostics
24816
+ : [];
24817
+ return diagnostics.map((d) => ({
24818
+ code: d.code,
24819
+ severity: d.severity,
24820
+ path: d.path,
24821
+ message: d.message,
24822
+ }));
24823
+ },
24824
+ save: async (project) => {
24825
+ const result = await saveProject(project);
24826
+ if (result.ok) {
24827
+ return { ok: true };
24828
+ }
24829
+ return { ok: false, code: result.code, message: result.message };
24830
+ },
24831
+ dryRun: async (currentProject, cases, options) => {
24832
+ const res = await fetch(apiBase + '/api/dry-run', {
24833
+ method: 'POST',
24834
+ headers: {
24835
+ 'Content-Type': 'application/json',
24836
+ 'X-CSRF-Token': csrfToken,
24837
+ },
24838
+ body: JSON.stringify({ project: currentProject, cases, options }),
24839
+ });
24840
+ return res.json();
24841
+ },
24842
+ onCancel: () => {
24843
+ cancel();
24844
+ },
24845
+ ${aiAssistHandler}
24846
+ });
24847
+ void editor;
24848
+ } catch (error) {
24849
+ const parts = [];
24850
+ if (error && typeof error === 'object') {
24851
+ if ('message' in error) parts.push(String(error.message));
24852
+ if (Array.isArray(error.diagnostics)) {
24853
+ for (const diagnostic of error.diagnostics) {
24854
+ if (
24855
+ diagnostic &&
24856
+ typeof diagnostic === 'object' &&
24857
+ typeof diagnostic.message === 'string'
24858
+ ) {
24859
+ parts.push(diagnostic.message);
24860
+ }
24861
+ }
24696
24862
  }
24697
- return { ok: false, code: result.code, message: result.message };
24698
- },
24699
- dryRun: async (currentProject, cases, options) => {
24700
- const res = await fetch(apiBase + '/api/dry-run', {
24701
- method: 'POST',
24702
- headers: {
24703
- 'Content-Type': 'application/json',
24704
- 'X-CSRF-Token': csrfToken,
24705
- },
24706
- body: JSON.stringify({ project: currentProject, cases, options }),
24707
- });
24708
- return res.json();
24709
- },
24710
- onCancel: () => {
24711
- cancel();
24712
- },
24713
- ${aiAssistHandler}
24714
- });
24863
+ }
24864
+ root.textContent = parts.join(' \u2014 ') || 'Rogatio editor could not initialize';
24865
+ }
24715
24866
  </script>
24716
24867
  </body>
24717
24868
  </html>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogatio/cli",
3
- "version": "5.13.2",
3
+ "version": "5.14.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local-first browser request and response rules — editor host, file verification, test runner, and runtime dispatch.",