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 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. 'skill-host' hosts permission-gated
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 to
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`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rewritable",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "CLI for re-writeable: emit and import single-file rwa documents.",
5
5
  "type": "module",
6
6
  "bin": {