altalandi 0.6.1 → 0.6.3

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
@@ -99,6 +99,8 @@ altalandi --fast "..." # skip the meaning + nativeness gates
99
99
  altalandi --diff --file draft.txt # show before, after and every change with its reason
100
100
  altalandi --review --file in.txt --out out.txt # for agents: one block to show as it is
101
101
  altalandi --report --file draft.txt # also write a Markdown report to ./altalandi-reports/
102
+ altalandi --review --job j_20261013a1b2c3 --job-title "Handbók" --part "Hluti 1 af 3" --file part1.txt
103
+ # one part of a longer text: same --job on every part
102
104
  ```
103
105
 
104
106
  | flag | default | meaning |
@@ -107,11 +109,14 @@ altalandi --report --file draft.txt # also write a Markdown report to ./altal
107
109
  | `--register` | `neutral` | `formal` \| `neutral` \| `warm` |
108
110
  | `--json` | off | print the full API response |
109
111
  | `--diff` | off | print the text before and after, then each change with its reason, on `stderr` (coloured in a terminal; `NO_COLOR` turns colour off) |
110
- | `--review` | off | print one Markdown block on `stdout`, made to be shown as it is: the verdict, the text before and after, each change with its reason, what the call cost and the balance and free units left. Exits on the outcome: `0` rewritten and verified or no change needed, `3` refused or unverified, `4` out of credit |
111
- | `--no-store` | off | send `contribute: false`: this text is never kept. Without it, free use (calls covered by the free daily units) is kept with personal data scrubbed, to improve the service; paid use is kept only with consent ([privacy](https://altalandi.is/personuvernd#textabanki)) |
112
+ | `--review` | off | print one Markdown block on `stdout`, made to be shown as it is: the verdict, the text before and after, each change with its reason, what the call cost and the balance and free characters left. Exits on the outcome: `0` rewritten and verified or no change needed, `3` refused or unverified, `4` out of credit |
113
+ | `--no-store` | off | send `contribute: false`: this text is never kept. Without it, free use (calls covered by the free daily characters) is kept with personal data scrubbed, to improve the service; paid use is kept only with consent ([privacy](https://altalandi.is/personuvernd#textabanki)) |
112
114
  | `--out <file>` | | write the refined text to `<file>` (UTF-8); with `--review` the block names it when the text is shortened |
113
115
  | `--report` | off | also write `./altalandi-reports/altalandi-<time>-<id>.md`: input, output, a table of every change (before, after, reason, layer), warnings and the verdict |
114
116
  | `--report-dir <dir>` | | write the report to `<dir>` instead (implies `--report`) |
117
+ | `--job <id>` | | make this call a part of a job (verk): every call with the same id is one job on the site, one page for the whole text. The id is yours to choose: `j_` and 8–64 of `A-Z a-z 0-9 _ -`, e.g. `j_` + the date + a few random characters. With `--review`, the block links the job (`Verkið`) and this part (`Hlutinn`) |
118
+ | `--job-title <text>` | | the job's name (up to 120 characters); the first call that sends one names it |
119
+ | `--part <text>` | | this call's part, e.g. `Hluti 2 af 5` (up to 300 characters); needs `--job` |
115
120
  | `--fast` | off | `preserveMeaning:false`, `scoreNativeness:false`, `onlyImprove:false`, `polishPass:false`, `scoring:"none"` — normally one model call (structured input can still add a structure-preserving retry) |
116
121
 
117
122
  ## Folders, i18n catalogs and websites
@@ -127,8 +132,11 @@ altalandi https://example.com/pricing --out pricing-is.md # one page → Markd
127
132
  altalandi https://example.com/docs/ --site --max-pages 50 # a site → ./example.com-is/
128
133
  ```
129
134
 
130
- Each passage (a paragraph, list item, table cell, heading or string) is its own
131
- call. Everything outside the passages is written back byte for byte:
135
+ Each passage (a paragraph, list item, table cell, heading or string) is refined
136
+ and checked on its own. Short passages (up to 1,500 characters) are sent
137
+ together through `/v1/refine/batch`, up to 40 a request and 6,000 characters;
138
+ longer ones go one call each to `/v1/refine`. Everything outside the passages is
139
+ written back byte for byte:
132
140
 
133
141
  | format | refined | never touched |
134
142
  |--------|---------|---------------|
@@ -154,13 +162,19 @@ the same origin and under the URL's folder.
154
162
  from the public tariff:
155
163
 
156
164
  ```text
157
- 5 files, 24 passages, about 851 characters (23 calls: repeated passages are sent once).
165
+ 5 files, 24 passages, about 851 characters (23 to send: repeated passages are sent once).
166
+ 1 request: 23 short passages together in 1 batch.
158
167
  1 more file has nothing to refine and is copied unchanged.
159
- Billing: about 920 units (at least 40 a call), about 74 kr if paid from credit. The free allowance is 2,000 units a day.
168
+ Kostnaður: um 3.016 stafir (um 1 A4-síða), um 30 kr. ef greitt er af inneign.
169
+ Hvert kall telst minnst 250 stafir; margir stuttir textar í einu kalli minnst 130 stafir hver. Hver aðgangur fær ókeypis dagskammt af stöfum (`GET /v1/pricing`).
160
170
  ```
161
171
 
162
- Every call costs at least the per-call minimum, so many short strings cost more
163
- than their word count. In a terminal it asks before sending; without one (CI,
172
+ Billing is by character (stafir): every character sent, spaces and punctuation
173
+ included. In a batch each string is billed at least 130 characters, and the
174
+ 250-character per-call minimum is paid once per request, not once per string:
175
+ short strings cost about half what one call each would. The summary and the
176
+ report show the characters actually billed. An older self-hosted engine without the batch endpoint gets one
177
+ call per passage (the run says so once). In a terminal it asks before sending; without one (CI,
164
178
  agents) it sends nothing unless you pass `--yes`.
165
179
 
166
180
  | flag | default | meaning |
@@ -169,15 +183,24 @@ agents) it sends nothing unless you pass `--yes`.
169
183
  | `--dry-run` | off | list every passage and the estimate; send nothing, write nothing |
170
184
  | `--yes`, `-y` | off | send without asking |
171
185
  | `--max-chars <n>` | | files are sent in order until the next would go over n characters; the rest are neither sent nor written |
172
- | `--concurrency <n>` | `2` | calls at once, 1–4 |
186
+ | `--concurrency <n>` | `2` | requests at once, 1–4 |
173
187
  | `--site` / `--max-pages <n>` | off / `20` | read a site from the URL, up to n pages |
174
188
  | `--no-store` | off | keep none of the passages (free use is otherwise kept, scrubbed of personal data) |
175
189
  | `--json` | off | a machine-readable summary of every file and passage on stdout |
190
+ | `--job <id>` / `--job-title <text>` | a new job | add the run to an existing job, or name it |
176
191
 
177
192
  A `429` waits as long as `Retry-After` asks (up to 5 minutes) and retries. A
178
193
  `402` stops the run cleanly: finished files are written, the rest are listed and
179
194
  not written; run the same command again after topping up.
180
195
 
196
+ **One job.** A run is one job (verk) on your account: every request sends the
197
+ same `job` id (`j_` + the date + 10 random characters, made per run), titled by
198
+ the folder's name or the site's host, and each file (its relative path) or page
199
+ (its URL) is a part. A batch that holds several small files is named by its
200
+ first file; a file that fits in one batch is not split across two. The summary
201
+ and the report show one `Verkið: <link>` to the job's page, not one link per
202
+ call. An engine that does not know jobs simply leaves the line out.
203
+
181
204
  **Results.** Only verified changes are written. A passage keeps its original
182
205
  text when its meaning check did not pass (`meaningCheck` other than `passed`, or
183
206
  `meaningPreserved` not `true`), or when the refined text lost or changed code, a
@@ -255,6 +278,6 @@ the explicit verification opt-out and exits 0 after a successful API response.
255
278
 
256
279
  MIT licensed. Calls are paid from the account's prepaid credit (a 500 kr trial,
257
280
  then credit bought in the portal); a call the credit cannot cover exits with the
258
- server's `402 insufficient_balance` message. The request's text and glossary fields are charged at the effective billing-tier
259
- weight. Each whitespace-delimited token is at least one unit; an unbroken token
260
- uses one unit per 20 characters, rounded up.
281
+ server's `402 insufficient_balance` message. The request's text and glossary fields are charged by the character
282
+ (every Unicode character, spaces, punctuation and line breaks included) at the
283
+ effective billing-tier weight, with a minimum per call.
package/altalandi.mjs CHANGED
@@ -108,6 +108,9 @@ for (let i = 0; i < args.length; i++) {
108
108
  else if (a === "--yes" || a === "-y") opts.yes = true;
109
109
  else if (a === "--max-chars") opts.maxChars = Number(args[++i]);
110
110
  else if (a === "--concurrency") opts.concurrency = Number(args[++i]);
111
+ else if (a === "--job") opts.job = args[++i] ?? "";
112
+ else if (a === "--job-title") opts.jobTitle = args[++i] ?? "";
113
+ else if (a === "--part") opts.part = args[++i] ?? "";
111
114
  else positional.push(a);
112
115
  }
113
116
 
@@ -122,6 +125,9 @@ Usage:
122
125
  for agents: one block to show as it is
123
126
  echo "text" | altalandi
124
127
  altalandi --domain marketing --register warm "text"
128
+ altalandi --review --job j_20261013a1b2c3 --job-title "Handbók" --part "Hluti 1 af 3" --file part1.txt
129
+ one part of a longer text: every part with the same
130
+ --job is one verk (job) on the site
125
131
 
126
132
  Folders and websites:
127
133
  altalandi ./docs every .md .txt .html .json under ./docs → ./docs-is
@@ -154,6 +160,10 @@ Options:
154
160
  --report also write a Markdown report (input, output, changes, reasons, verdict)
155
161
  to ./altalandi-reports/ (or --report-dir <dir>; ALTALANDI_REPORT=1 turns it on)
156
162
  --file read the text from a UTF-8 file instead of an argument
163
+ --job a job id (j_ and 8-64 of A-Z a-z 0-9 _ -) you choose: calls with the same id
164
+ are the parts of one verk; --review then links the job and this part
165
+ --job-title the job's name, kept from the first call that sends one (up to 120 characters)
166
+ --part this call's part, e.g. "Hluti 2 af 5" (up to 300 characters; needs --job)
157
167
  --base-url API base (default $ALTALANDI_BASE_URL or https://altalandi.is/api)
158
168
 
159
169
  stdout = refined text; stderr = nativeness / meaning / warnings / diff.
@@ -169,6 +179,9 @@ Folder and website options:
169
179
  --site with a URL: its pages on the same site, under the URL's folder
170
180
  --max-pages pages to read with --site (default 20)
171
181
  --json print a machine-readable summary at the end
182
+ --job add the run to this job; --job-title names it (default: a new
183
+ job per run, named by the folder or the site's host; each file
184
+ or page is a part)
172
185
  Code, URLs, link targets, placeholders ({name}, %s, ICU), HTML tags and JSON keys
173
186
  stay byte for byte. Only changes whose meaning check passed are written; other
174
187
  passages keep the original text and are listed in the report
@@ -186,6 +199,13 @@ function readStdin() {
186
199
  });
187
200
  }
188
201
 
202
+ // --job / --job-title / --part: checked here so a bad id never reaches the API.
203
+ const JOB_ID = /^j_[A-Za-z0-9_-]{8,64}$/;
204
+ if (opts.job !== undefined && !JOB_ID.test(opts.job)) {
205
+ console.error(`--job must be j_ followed by 8 to 64 letters, digits, _ or - (for example j_20261013a1b2c3); got "${opts.job}".`);
206
+ process.exit(2);
207
+ }
208
+
189
209
  const stored = readCredentials();
190
210
  const baseUrl = (opts.baseUrl || process.env.ALTALANDI_BASE_URL || stored?.baseUrl || "https://altalandi.is/api").replace(/\/$/, "");
191
211
  const { key } = resolveKey({ flag: opts.key });
@@ -206,6 +226,10 @@ if (opts.site || opts.dryRun || opts.maxChars !== undefined || opts.maxPages !==
206
226
  console.error("--site, --dry-run, --max-chars, --max-pages and --concurrency need a folder or a URL: altalandi ./docs --dry-run");
207
227
  process.exit(2);
208
228
  }
229
+ if (opts.job === undefined && (opts.jobTitle !== undefined || opts.part !== undefined)) {
230
+ console.error("--job-title and --part need --job <id>: the same id on every part of the text.");
231
+ process.exit(2);
232
+ }
209
233
 
210
234
  let text = positional.join(" ").trim();
211
235
  if (opts.file) {
@@ -224,6 +248,14 @@ const requestOptions = {
224
248
  register: opts.register,
225
249
  ...(opts.noStore ? { contribute: false } : {}),
226
250
  };
251
+ if (opts.job) {
252
+ const cap = (s, n) => [...String(s)].slice(0, n).join("");
253
+ requestOptions.job = {
254
+ id: opts.job,
255
+ ...(opts.jobTitle ? { title: cap(opts.jobTitle, 120) } : {}),
256
+ ...(opts.part ? { part: cap(opts.part, 300) } : {}),
257
+ };
258
+ }
227
259
  // --fast disables every optional quality/verification pass. The writer can
228
260
  // still retry once when it drops document structure, as documented in --help.
229
261
  // Name every switch explicitly so a future server-default change cannot make
package/auth.mjs CHANGED
@@ -211,10 +211,8 @@ export async function runWhoami(flags = {}, io = {}) {
211
211
  const me = await res.json();
212
212
  if (flags.json) out(JSON.stringify({ ...me, keySource: source }, null, 2));
213
213
  else {
214
- const { formatInt, formatIsk } = await import("./report.mjs");
215
- const free = me.freeUnitsLeftToday != null && me.freeDailyUnits != null
216
- ? `ókeypis eftir í dag ${formatInt(me.freeUnitsLeftToday)} af ${formatInt(me.freeDailyUnits)} einingum`
217
- : me.freeDailyUnits ? `ókeypis ${formatInt(me.freeDailyUnits)} einingar á dag` : "";
214
+ const { formatIsk, freeCharsText } = await import("./report.mjs");
215
+ const free = freeCharsText(null, me);
218
216
  out([me.username ?? "(lykill án aðgangs)",
219
217
  `inneign ${me.balanceIsk != null ? formatIsk(me.balanceIsk) : "—"}`,
220
218
  free, `lykill ${me.keyId} (${source})`].filter(Boolean).join(" · "));