rewritable 0.8.1 → 0.10.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/bin/rwa.mjs CHANGED
@@ -71,6 +71,16 @@ Usage:
71
71
  editing — the returned url carries the capability
72
72
  token in its #k= fragment. Target: --url >
73
73
  \$RWA_HOST_URL. --json emits {id,token,url}.
74
+ rwa install <skill> <host> install a signed .rwa-skill.json into a skill-host
75
+ container, offline. Verifies the signature + runs the
76
+ same trust gates as the in-app dialog, then splices the
77
+ skill into the frozen #rwa-skills zone. Requires --yes
78
+ (no dialog to consent in); gate failures are final.
79
+ --json emits {skillId,name,kind,verified,status}.
80
+ rwa skill publish <file> publish a SIGNED .rwa-skill.json to the marketplace
81
+ index (POST /skills/publish). The envelope is already
82
+ signed — no key needed. Online; --url overrides the
83
+ service, --json emits {skillId,registryUrl,verified}.
74
84
  rwa skin <path> <name> apply a named style preset to a rewritable in
75
85
  place (deterministic, offline, model-free). Names:
76
86
  notion-clean, linear-dark, editorial-serif,
@@ -715,6 +725,60 @@ function detectProductKind(fileText) {
715
725
  return;
716
726
  }
717
727
 
728
+ // `rwa skill publish <file.rwa-skill.json> [--url base] [--json]` — publish a SIGNED skill
729
+ // envelope to the marketplace index (POST /skills/publish, I6 §11). The envelope is already
730
+ // signed (no key needed). Online by design; exit 4 labeled `publish_error` (like `publish`).
731
+ if (verb === 'skill') {
732
+ const sub = rest[0];
733
+ const subRest = rest.slice(1);
734
+ if (sub !== 'publish') {
735
+ process.stderr.write("rwa skill: unknown subcommand '" + (sub || '') + "' (try: rwa skill publish <file>)\n");
736
+ process.exitCode = 1;
737
+ return;
738
+ }
739
+ const jsonMode = subRest.includes('--json');
740
+ const urlFlag = getFlag('--url', subRest);
741
+ const urlIdx = subRest.indexOf('--url');
742
+ const skip = urlIdx >= 0 ? urlIdx + 1 : -1;
743
+ const filePath = subRest.find((a, i) => !a.startsWith('-') && i !== skip);
744
+ const emitSP = (payload) => {
745
+ if (jsonMode) { process.stderr.write(JSON.stringify(payload) + '\n'); return; }
746
+ const parts = [payload.code, payload.subcode].filter(Boolean);
747
+ let line = 'rwa skill publish: ' + parts.join('/');
748
+ if (payload.details && Object.keys(payload.details).length) line += ' ' + JSON.stringify(payload.details);
749
+ process.stderr.write(line + '\n');
750
+ };
751
+ if (!filePath) { emitSP({ code: 'usage_error', subcode: 'missing_file_arg' }); process.exitCode = 1; return; }
752
+ if (urlFlag.present && (urlFlag.value === undefined || urlFlag.value.startsWith('-'))) {
753
+ emitSP({ code: 'usage_error', subcode: 'missing_flag_value', details: { flag: '--url' } }); process.exitCode = 1; return;
754
+ }
755
+ const baseUrl = urlFlag.value || process.env.RWA_PUBLISH_URL || undefined;
756
+ const { skillPublishCmd } = await import('../src/skill-publish.mjs');
757
+ let result;
758
+ try {
759
+ result = await skillPublishCmd(filePath, { baseUrl });
760
+ } catch (e) {
761
+ if (e && typeof e.exitCode === 'number') {
762
+ const code = e.exitCode === 4 ? 'publish_error' : codeName(e.exitCode);
763
+ emitSP({ code, subcode: e.subcode, details: e.details });
764
+ process.exitCode = e.exitCode;
765
+ return;
766
+ }
767
+ throw e;
768
+ }
769
+ if (jsonMode) {
770
+ process.stdout.write(JSON.stringify(result) + '\n');
771
+ } else {
772
+ process.stdout.write(
773
+ '✓ Published skill to the index!\n' +
774
+ ` skillId: ${result.skillId}\n` +
775
+ ` URL: ${result.registryUrl}\n` +
776
+ ` verified: ${result.verified}\n`,
777
+ );
778
+ }
779
+ return;
780
+ }
781
+
718
782
  // `rwa publish-site <file> [--host h] [--path p] [--url base] [--json]` —
719
783
  // copy a rewritable VERBATIM onto a static site over scp; print the live URL.
720
784
  // Durable counterpart to `rwa publish` (ephemeral share). Online by design.
@@ -826,6 +890,66 @@ function detectProductKind(fileText) {
826
890
  return;
827
891
  }
828
892
 
893
+ // `rwa install <skill.rwa-skill.json> <skill-host.html> [--yes|--trust] [--json]` (v0.9 §3 / I11).
894
+ // The offline, headless counterpart of the seed's install dialog: verify the Ed25519
895
+ // signature, run the SAME gates (unsigned-capability / compute-with-perms / permission
896
+ // grammar / dynamic-import reject), then splice the envelope into the frozen #rwa-skills
897
+ // zone and write atomically. No dialog to consent in → an explicit --yes/--trust is
898
+ // required, and gate failures (exit 3) are FINAL — --yes cannot override them. Exit codes:
899
+ // 1 usage, 2 file, 3 envelope/gate (reuses codeName — no verb-specific exit-4 class). See
900
+ // src/install.mjs. The CLI is the sole audited exception to runtime-sole-writer (Inv 39).
901
+ if (verb === 'install') {
902
+ const jsonMode = rest.includes('--json');
903
+ const consent = rest.includes('--yes') || rest.includes('--trust');
904
+ const [envPath, hostPath] = rest.filter((a) => !a.startsWith('-'));
905
+ const emitInstall = (payload) => {
906
+ if (jsonMode) { process.stderr.write(JSON.stringify(payload) + '\n'); return; }
907
+ const parts = [payload.code, payload.subcode].filter(Boolean);
908
+ let line = 'rwa install: ' + parts.join('/');
909
+ if (payload.details && Object.keys(payload.details).length) line += ' ' + JSON.stringify(payload.details);
910
+ process.stderr.write(line + '\n');
911
+ };
912
+ if (!envPath || !hostPath) {
913
+ emitInstall({ code: 'usage_error', subcode: 'missing_file_args', details: { usage: 'rwa install <skill.rwa-skill.json> <skill-host.html> [--yes] [--json]' } });
914
+ process.exitCode = 1;
915
+ return;
916
+ }
917
+ const { installSkillFile } = await import('../src/install.mjs');
918
+ let result;
919
+ try {
920
+ result = await installSkillFile(envPath, hostPath, { consent });
921
+ } catch (e) {
922
+ if (e && typeof e.exitCode === 'number') {
923
+ emitInstall({ code: codeName(e.exitCode), subcode: e.subcode, details: e.details });
924
+ process.exitCode = e.exitCode;
925
+ return;
926
+ }
927
+ throw e;
928
+ }
929
+ // Non-blocking lookalike warning (spec §3 / Inv 23) → always to stderr; --json also
930
+ // carries result.lookalike in the stdout object. The install already succeeded.
931
+ if (result.lookalike) {
932
+ process.stderr.write('⚠ rwa install: the name "' + result.name + '" closely matches "' + result.lookalike + '", installed from a DIFFERENT key. The author is identified by the key, not the name — review before trusting.\n');
933
+ }
934
+ // I5 — same-key rename heads-up (non-blocking, registry-derived). This author published other
935
+ // names in this host; surfaced so a rename reads as continuity, not a new author.
936
+ if (Array.isArray(result.priorNames) && result.priorNames.length) {
937
+ process.stderr.write('note: rwa install: this author (same key) previously published: ' + result.priorNames.join(', ') + '. The current name is "' + result.name + '" — identity is the key, not the name.\n');
938
+ }
939
+ if (jsonMode) {
940
+ process.stdout.write(JSON.stringify(result) + '\n');
941
+ } else {
942
+ const label = result.status === 'updated' ? 'Updated' : result.status === 'already_installed' ? 'Already installed' : 'Installed';
943
+ process.stdout.write(
944
+ '✓ ' + label + ' ' + result.name + ' (' + result.kind + ', ' + (result.verified ? 'verified' : 'UNVERIFIED') + ')\n' +
945
+ ' skillId: ' + result.skillId + '\n' +
946
+ (result.update && result.update.added.length ? ' + added: ' + result.update.added.join(', ') + '\n' : '') +
947
+ (result.update && result.update.removed.length ? ' − removed: ' + result.update.removed.join(', ') + '\n' : ''),
948
+ );
949
+ }
950
+ return;
951
+ }
952
+
829
953
  // `rwa skin <file> <name|reset> [--l1] [--theme-only] [--json]`.
830
954
  //
831
955
  // DEFAULT (no --l1): deterministic, model-free theme swap. Applies a preset's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rewritable",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "CLI for re-writeable: emit and import single-file rwa documents.",
5
5
  "type": "module",
6
6
  "bin": {