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 +124 -0
- package/package.json +1 -1
- package/seeds/rewritable.html +873 -30
- package/src/identity.mjs +3 -2
- package/src/install.mjs +206 -0
- package/src/skill-manifest.mjs +239 -4
- package/src/skill-publish.mjs +59 -0
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
|