rewritable 0.6.0 → 0.8.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 +24 -4
- package/bin/rwa.mjs +56 -11
- package/package.json +1 -1
- package/seeds/rewritable.html +885 -48
- package/src/agent-loop.mjs +5 -3
- package/src/backend.mjs +24 -6
- package/src/commands.mjs +1 -1
- package/src/create.mjs +2 -1
- package/src/identity.mjs +1 -0
- package/src/seed.mjs +84 -1
- package/src/workspace.mjs +262 -0
package/README.md
CHANGED
|
@@ -31,6 +31,8 @@ rwa edit notes.html --plan plan.json # envelope from a file
|
|
|
31
31
|
|
|
32
32
|
rwa doc notes.html # print the editable body
|
|
33
33
|
rwa doc notes.html --json # read + edit-contract, one call
|
|
34
|
+
rwa workspace create research # → research/rwa-index.html
|
|
35
|
+
rwa workspace sync research # refresh the index from sibling rewritables
|
|
34
36
|
|
|
35
37
|
rwa publish notes.html # → a hosted 24h share URL
|
|
36
38
|
rwa host notes.html --url https://host.example # → {id, token, url} (round-trip editing)
|
|
@@ -49,6 +51,7 @@ Pass `--kind <name>` to scaffold a different primary stance at first paint:
|
|
|
49
51
|
- `--kind document` (default) — prose container; lens placeholder *"Write, or describe what you want."*
|
|
50
52
|
- `--kind workflow` — three-stage scaffold (Inbox / In progress / Done); lens placeholder *"Add an item, or describe a stage move."*
|
|
51
53
|
- `--kind presentation` — prose slide deck (split on `h1`/`h2`); the *Present* toggle renders it as slides at view time without changing the stored text (spec §5.10); lens placeholder *"Add a slide, or describe a change."*
|
|
54
|
+
- `--kind workspace` — directory control center scaffold. Prefer `rwa workspace create <dir>` for real workspaces so the generated manifest is filled from the sibling rewritables on disk.
|
|
52
55
|
- `--kind skill-host` — hosts permission-gated skills installed from `.rwa-skill.json` files; ships an empty runtime-owned frozen `#rwa-skills` zone the runtime (never the agent) rewrites on install/uninstall; installed skills are reported via `rwa doc`/`ls` as `tool`/`compute` affordances (`provenance:'installed'`). See `docs/specs/re-write-able-actions-spec-v0.8.md` §2.
|
|
53
56
|
|
|
54
57
|
The product-kind taxonomy is documented at `docs/specs/rwa-product-types.md` in the main repo. The substrate runtime is unchanged across kinds — only the `INLINE_DOC` body and lens placeholder vary at emit time.
|
|
@@ -125,10 +128,10 @@ The agent loop retries up to 3 times when the model emits plain text instead of
|
|
|
125
128
|
|
|
126
129
|
| Flag | Effect |
|
|
127
130
|
|---|---|
|
|
128
|
-
| `--backend <name>` | `openrouter` (default), `ollama`, `lmstudio`. Falls back to `$RWA_BACKEND`. `bridge` is browser-only by design. |
|
|
131
|
+
| `--backend <name>` | `openrouter` (default), `ollama`, `lmstudio`, `atomic`. Falls back to `$RWA_BACKEND`. `bridge` is browser-only by design. |
|
|
129
132
|
| `--model <id>` | model id passed to the backend. Falls back to `$RWA_MODEL`, then `google/gemini-3.5-flash`. |
|
|
130
|
-
| `--base-url <url>` | OpenAI-compatible base URL override. Defaults: `https://openrouter.ai/api/v1`, `http://localhost:11434/v1` (or `$RWA_OLLAMA_URL`), `http://localhost:1234/v1` (or `$RWA_LMSTUDIO_URL`). |
|
|
131
|
-
| `--api-key <key>` | openrouter only; falls back to `$RWA_OPENROUTER_KEY`. ollama / lmstudio run locally without auth. |
|
|
133
|
+
| `--base-url <url>` | OpenAI-compatible base URL override. Defaults: `https://openrouter.ai/api/v1`, `http://localhost:11434/v1` (or `$RWA_OLLAMA_URL`), `http://localhost:1234/v1` (or `$RWA_LMSTUDIO_URL`), `http://127.0.0.1:1337/v1` (or `$RWA_ATOMIC_URL`). |
|
|
134
|
+
| `--api-key <key>` | openrouter only; falls back to `$RWA_OPENROUTER_KEY`. ollama / lmstudio / atomic run locally without auth. |
|
|
132
135
|
|
|
133
136
|
#### Other edit flags
|
|
134
137
|
|
|
@@ -180,6 +183,23 @@ rwa ls a.html b.html # …an explicit list
|
|
|
180
183
|
|
|
181
184
|
`--json` emits an array of rows for an agent — `{file, status, self}` where `status` is `rewritable` (with the full `self-description/1` object), `not_a_rewritable`, or `error` (with a `reason`). The scan is lenient like its namesake: one bad path among many is a row, not a fatal exit, so a completed scan exits `0`. This is how an agent handed a project learns its whole rewritable inventory — and every container's affordances — in a single call.
|
|
182
185
|
|
|
186
|
+
### `rwa workspace create <dir>` / `rwa workspace sync [dir]`
|
|
187
|
+
|
|
188
|
+
Create or refresh a folder-level control center at `<dir>/rwa-index.html`. The index is itself a rewritable of kind `workspace`: it contains editable shared context for the directory (workspace memory, guidelines, examples, open questions), opens as a dashboard of sibling rewritable documents, and carries a frozen `<script id="rwa-workspace" type="application/rwa-workspace+json">` manifest generated from the directory inventory.
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
rwa workspace create notes/
|
|
192
|
+
rwa new notes/project-brief.html
|
|
193
|
+
rwa new notes/research-log.html
|
|
194
|
+
rwa workspace sync notes/
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`create` makes the directory if needed and refuses to overwrite an existing `rwa-index.html` unless `--force` is passed. `sync` refreshes an existing index, or creates it if absent, using the current sibling `.html` rewritables in that directory. The scan is non-recursive and skips non-rewritable HTML files plus the index itself. The editable context block is preserved across sync, so notes like blog style guidelines and canonical examples survive inventory refreshes.
|
|
198
|
+
|
|
199
|
+
This first pass is intentionally simple: it does not merge documents, schedule automations, or expand the skill-host runtime. It gives a directory a portable, editable shared brain plus a truthful machine-readable manifest that other rewritables can use as context.
|
|
200
|
+
|
|
201
|
+
When the workspace index is open, it also listens on the runtime bus for same-directory rewritables that are currently open. Those live documents appear under **Open now** and are marked `new since sync` until the next `rwa workspace sync` writes them into the durable manifest. This is live presence only: unopened files still require `sync` because a browser page cannot enumerate arbitrary local directories by itself.
|
|
202
|
+
|
|
183
203
|
### `rwa publish <path>`
|
|
184
204
|
|
|
185
205
|
Publish a local rewritable to the hosted share service and get back a URL. A rewritable is already shareable as a file — it's a self-contained `.html` you can email or host anywhere — but `rwa publish` is the one-command path to an *anonymous, hosted* snapshot: create with `rwa new`, edit locally, publish.
|
|
@@ -293,7 +313,7 @@ Because the anchors in step 1 are the *same* text step 3 splices against, what t
|
|
|
293
313
|
|---|---|
|
|
294
314
|
| `--force`, `-f` | overwrite the destination if it exists |
|
|
295
315
|
| `--open`, `-o` | open the resulting file in the default app |
|
|
296
|
-
| `--kind <name>` | (`rwa new` only) starter kind: `document` (default), `workflow`, `presentation`, `skill-host` |
|
|
316
|
+
| `--kind <name>` | (`rwa new` only) starter kind: `document` (default), `workflow`, `presentation`, `workspace`, `skill-host` |
|
|
297
317
|
| `--version` | print version |
|
|
298
318
|
| `--help`, `-h` | usage |
|
|
299
319
|
|
package/bin/rwa.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { newCmd, importCmd, version, KNOWN_KINDS, openWithPrefill, SEED_CANDIDATES } from '../src/commands.mjs';
|
|
3
|
-
import { resolveApiKey } from '../src/backend.mjs';
|
|
3
|
+
import { resolveApiKey, backendMaxTokens } from '../src/backend.mjs';
|
|
4
4
|
import { parseCreateArgs, createCmd } from '../src/create.mjs';
|
|
5
5
|
import { relative } from 'node:path';
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ Usage:
|
|
|
12
12
|
rwa new <name> [path] a bare <name> resolves template-first: clone a cwd
|
|
13
13
|
file labeled data-rwa-template="<name>" (fresh UUID,
|
|
14
14
|
label stripped) if one exists; else, if <name> is a
|
|
15
|
-
known kind (document/workflow/presentation/skill-host), create
|
|
15
|
+
known kind (document/workflow/presentation/workspace/skill-host), create
|
|
16
16
|
that built-in kind. So "rwa new presentation" makes a
|
|
17
17
|
deck, and your own labeled file overrides the builtin.
|
|
18
18
|
Default out: ./<name>-YYYY-MM-DD.html.
|
|
@@ -52,6 +52,12 @@ Usage:
|
|
|
52
52
|
to \`rwa doc\`. Non-rewritables are counted, not
|
|
53
53
|
hidden. With --json, an array of self-description
|
|
54
54
|
rows. Lenient: a completed scan exits 0.
|
|
55
|
+
rwa workspace create <dir> create a folder-level rwa-index.html control
|
|
56
|
+
center. The index is a rewritable of kind
|
|
57
|
+
workspace, with a frozen manifest generated from
|
|
58
|
+
sibling rewritables in that directory.
|
|
59
|
+
rwa workspace sync [dir] refresh <dir>/rwa-index.html from the current
|
|
60
|
+
sibling rewritable inventory (default: ./).
|
|
55
61
|
rwa publish <path> publish a local rewritable to the share service
|
|
56
62
|
and print the hosted URL. POSTs your edited bytes;
|
|
57
63
|
the hosted snapshot is anonymous, 24h, with a fresh
|
|
@@ -84,11 +90,13 @@ Usage:
|
|
|
84
90
|
|
|
85
91
|
Flags:
|
|
86
92
|
--kind <name> (new only) starter kind: document (default), workflow,
|
|
87
|
-
presentation, or skill-host. 'document' is the canonical prose
|
|
93
|
+
presentation, workspace, or skill-host. 'document' is the canonical prose
|
|
88
94
|
container. 'workflow' scaffolds three stages (Inbox / In
|
|
89
95
|
progress / Done). 'presentation' scaffolds a prose deck that the
|
|
90
96
|
'Present' toggle displays as slides (split on h1/h2) without
|
|
91
|
-
changing the stored text. '
|
|
97
|
+
changing the stored text. 'workspace' scaffolds a directory
|
|
98
|
+
control center; prefer \`rwa workspace create\` so the manifest
|
|
99
|
+
is filled from disk. 'skill-host' hosts permission-gated
|
|
92
100
|
skills installed from .rwa-skill.json files (v0.8 actions spec).
|
|
93
101
|
See docs/specs/rwa-product-types.md.
|
|
94
102
|
--skin <name> (new only) bake a style preset into the new container:
|
|
@@ -101,7 +109,7 @@ Flags:
|
|
|
101
109
|
--open, -o open the resulting file in the default app. First-paint
|
|
102
110
|
sessionStorage is pre-populated from env / ./.env:
|
|
103
111
|
OPENROUTER_API_KEY → ?key=… (lifted into rwa_apikey)
|
|
104
|
-
RWA_BACKEND → ?backend= (openrouter|ollama|lmstudio|bridge)
|
|
112
|
+
RWA_BACKEND → ?backend= (openrouter|ollama|lmstudio|atomic|bridge)
|
|
105
113
|
RWA_MODEL → ?model=… (model name string)
|
|
106
114
|
The bootstrap lifts each into sessionStorage and scrubs the
|
|
107
115
|
URL bar on first paint, so the values don't sit in history.
|
|
@@ -137,8 +145,8 @@ Flags:
|
|
|
137
145
|
the raw body; on failure, the \`{code, subcode, details}\`
|
|
138
146
|
object goes to stderr.
|
|
139
147
|
--backend <n> (edit instruction path / skin --l1) backend name. One of:
|
|
140
|
-
openrouter (default), ollama, lmstudio. Falls back
|
|
141
|
-
\$RWA_BACKEND if unset.
|
|
148
|
+
openrouter (default), ollama, lmstudio, atomic. Falls back
|
|
149
|
+
to \$RWA_BACKEND if unset.
|
|
142
150
|
--model <id> (edit instruction path / skin --l1) model id passed to the
|
|
143
151
|
backend. Falls back to \$RWA_MODEL, then a
|
|
144
152
|
sensible default for the backend.
|
|
@@ -248,6 +256,7 @@ function envBaseUrl(name) {
|
|
|
248
256
|
case 'openrouter': return 'https://openrouter.ai/api/v1';
|
|
249
257
|
case 'ollama': return process.env.RWA_OLLAMA_URL || 'http://localhost:11434/v1';
|
|
250
258
|
case 'lmstudio': return process.env.RWA_LMSTUDIO_URL || 'http://localhost:1234/v1';
|
|
259
|
+
case 'atomic': return process.env.RWA_ATOMIC_URL || 'http://127.0.0.1:1337/v1';
|
|
251
260
|
default: return undefined;
|
|
252
261
|
}
|
|
253
262
|
}
|
|
@@ -377,7 +386,7 @@ function detectProductKind(fileText) {
|
|
|
377
386
|
|
|
378
387
|
// Reject unknown backends fast. `bridge` is browser-only by design
|
|
379
388
|
// (single-shot via web_cli_bridge); the CLI has no equivalent.
|
|
380
|
-
if (!['openrouter', 'ollama', 'lmstudio'].includes(backendName)) {
|
|
389
|
+
if (!['openrouter', 'ollama', 'lmstudio', 'atomic'].includes(backendName)) {
|
|
381
390
|
emitEdit({ code: 'usage_error', subcode: 'unknown_backend', details: { backend: backendName } }, jsonMode);
|
|
382
391
|
process.exitCode = 1;
|
|
383
392
|
return;
|
|
@@ -457,7 +466,7 @@ function detectProductKind(fileText) {
|
|
|
457
466
|
currentDoc: promptDoc,
|
|
458
467
|
instruction,
|
|
459
468
|
frozenZoneNames,
|
|
460
|
-
backend: { baseUrl, model: modelId, apiKey },
|
|
469
|
+
backend: { baseUrl, model: modelId, apiKey, maxTokens: backendMaxTokens(backendName) },
|
|
461
470
|
onRetry: r => {
|
|
462
471
|
if (jsonMode) {
|
|
463
472
|
process.stderr.write(JSON.stringify({ phase: 'retry', attempt: r.attempt, reason: r.reason }) + '\n');
|
|
@@ -605,6 +614,42 @@ function detectProductKind(fileText) {
|
|
|
605
614
|
return;
|
|
606
615
|
}
|
|
607
616
|
|
|
617
|
+
// `rwa workspace create <dir>` / `rwa workspace sync [dir]` — a directory
|
|
618
|
+
// control center. The generated rwa-index.html is itself a rewritable, but
|
|
619
|
+
// its manifest is CLI-owned and refreshed from sibling rewritables.
|
|
620
|
+
if (verb === 'workspace') {
|
|
621
|
+
const sub = rest.find(a => !a.startsWith('-'));
|
|
622
|
+
const force = rest.includes('--force') || rest.includes('-f');
|
|
623
|
+
const open = rest.includes('--open') || rest.includes('-o');
|
|
624
|
+
const positionals = rest.filter((a) => !a.startsWith('-'));
|
|
625
|
+
const dirPath = positionals[1] || '.';
|
|
626
|
+
const emitWorkspace = (msg) => { process.stderr.write('rwa workspace: ' + msg + '\n'); };
|
|
627
|
+
|
|
628
|
+
if (!sub || !['create', 'sync'].includes(sub)) {
|
|
629
|
+
emitWorkspace('usage: rwa workspace create <dir> [--force] [--open] | rwa workspace sync [dir] [--open]');
|
|
630
|
+
process.exitCode = 1;
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (sub === 'create' && !positionals[1]) {
|
|
634
|
+
emitWorkspace('missing <dir> argument');
|
|
635
|
+
process.exitCode = 1;
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const { workspaceCreateCmd, workspaceSyncCmd } = await import('../src/workspace.mjs');
|
|
640
|
+
try {
|
|
641
|
+
const result = sub === 'create'
|
|
642
|
+
? await workspaceCreateCmd({ dirPath, force, seedCandidates: SEED_CANDIDATES })
|
|
643
|
+
: await workspaceSyncCmd({ dirPath, seedCandidates: SEED_CANDIDATES });
|
|
644
|
+
console.log(`wrote ${relative(process.cwd(), result.indexPath) || result.indexPath} (${result.docs.length} document${result.docs.length === 1 ? '' : 's'})`);
|
|
645
|
+
if (open) await openWithPrefill(result.indexPath);
|
|
646
|
+
} catch (e) {
|
|
647
|
+
emitWorkspace((e && e.message) || String(e));
|
|
648
|
+
process.exitCode = (e && e.exitCode) || 1;
|
|
649
|
+
}
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
608
653
|
// `rwa publish <file> [--url <base>] [--json]` — publish a local rewritable
|
|
609
654
|
// to the service's snapshot endpoint and print the share URL. Thin client
|
|
610
655
|
// for `POST /publish`; see src/publish.mjs. Intentionally online (the
|
|
@@ -845,7 +890,7 @@ function detectProductKind(fileText) {
|
|
|
845
890
|
const baseUrl = baseUrlFlag.value || envBaseUrl(backendName);
|
|
846
891
|
const apiKey = resolveApiKey(backendName, apiKeyFlag.value);
|
|
847
892
|
|
|
848
|
-
if (!['openrouter', 'ollama', 'lmstudio'].includes(backendName)) {
|
|
893
|
+
if (!['openrouter', 'ollama', 'lmstudio', 'atomic'].includes(backendName)) {
|
|
849
894
|
emitSkin({ code: 'usage_error', subcode: 'unknown_backend', details: { backend: backendName } });
|
|
850
895
|
process.exitCode = 1; return;
|
|
851
896
|
}
|
|
@@ -882,7 +927,7 @@ function detectProductKind(fileText) {
|
|
|
882
927
|
result = await skinCmdL1(filePath, action, {
|
|
883
928
|
systemPrompt,
|
|
884
929
|
toolSchemas: TOOL_SCHEMAS,
|
|
885
|
-
backend: { baseUrl, model: modelId, apiKey },
|
|
930
|
+
backend: { baseUrl, model: modelId, apiKey, maxTokens: backendMaxTokens(backendName) },
|
|
886
931
|
onRetry: r => {
|
|
887
932
|
if (jsonMode) process.stderr.write(JSON.stringify({ phase: 'retry', attempt: r.attempt, reason: r.reason }) + '\n');
|
|
888
933
|
else process.stderr.write(`rwa skin: attempt ${r.attempt}/3 retrying — ${r.reason}\n`);
|