akm-cli 0.10.261005-beta → 0.10.261006-beta
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/CHANGELOG.md +38 -0
- package/STABILITY.md +9 -0
- package/dist/assets/ui/app.js +91 -0
- package/dist/assets/ui/asset.js +67 -0
- package/dist/assets/ui/bundles.js +80 -0
- package/dist/assets/ui/changes.js +32 -0
- package/dist/assets/ui/charts.js +93 -0
- package/dist/assets/ui/config.js +69 -0
- package/dist/assets/ui/diff-view.js +202 -0
- package/dist/assets/ui/diff.js +261 -0
- package/dist/assets/ui/index.html +30 -0
- package/dist/assets/ui/lib.js +238 -0
- package/dist/assets/ui/markdown.js +103 -0
- package/dist/assets/ui/overview.js +145 -0
- package/dist/assets/ui/proposal-actions.js +273 -0
- package/dist/assets/ui/proposals.js +219 -0
- package/dist/assets/ui/search.js +62 -0
- package/dist/assets/ui/styles.css +1415 -0
- package/dist/cli.js +3 -0
- package/dist/commands/health/checks.js +5 -3
- package/dist/commands/health/egress.js +5 -1
- package/dist/commands/health/engine-usage.js +2 -2
- package/dist/commands/health/llm-usage.js +2 -2
- package/dist/commands/health/windows.js +5 -5
- package/dist/commands/health.js +40 -8
- package/dist/commands/improve/reflect-noise.js +4 -0
- package/dist/commands/improve/reflect.js +14 -5
- package/dist/commands/proposal/repository.js +34 -29
- package/dist/commands/read/search.js +2 -1
- package/dist/commands/read/show.js +8 -6
- package/dist/commands/ui/api.js +556 -0
- package/dist/commands/ui/git-read.js +101 -0
- package/dist/commands/ui/open-browser.js +30 -0
- package/dist/commands/ui/proposal-actions.js +179 -0
- package/dist/commands/ui/redact.js +88 -0
- package/dist/commands/ui/server.js +352 -0
- package/dist/commands/ui/ui-cli.js +62 -0
- package/dist/indexer/search/db-search.js +2 -1
- package/dist/output/format-exempt.js +3 -0
- package/dist/storage/repositories/improve-runs-repository.js +6 -0
- package/dist/storage/repositories/metrics-repository.js +17 -0
- package/docs/architecture/0.10-process-rules.md +3 -4
- package/docs/architecture/improvement.md +2 -1
- package/docs/architecture/internals/improve-workflow.md +2 -2
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +49 -0
- package/docs/reference/ui-api.md +576 -0
- package/package.json +1 -1
|
@@ -112,7 +112,8 @@ export async function searchLocal(input) {
|
|
|
112
112
|
// Bootstrap-only: builds the index inline when it cannot serve this stash.
|
|
113
113
|
// Content freshness is the writers' job (indexWrittenAssets + full runs);
|
|
114
114
|
// reads serve the existing index as-is.
|
|
115
|
-
|
|
115
|
+
if (!input.readOnly)
|
|
116
|
+
await ensureIndex(stashDir);
|
|
116
117
|
const dbPath = getDbPath();
|
|
117
118
|
// An index we cannot READ is not an index that does not exist (#791). Saying
|
|
118
119
|
// "No search index available" for a populated index the caller merely lacks
|
|
@@ -41,6 +41,9 @@ const EXEMPT_COMMANDS = new Set([
|
|
|
41
41
|
"help",
|
|
42
42
|
// Embedded agent guide document (`src/output/cli-hints.ts`).
|
|
43
43
|
"hints",
|
|
44
|
+
// Prints the URL it is serving and then runs until interrupted; the data is
|
|
45
|
+
// the web UI's own JSON API, not a result envelope.
|
|
46
|
+
"ui",
|
|
44
47
|
]);
|
|
45
48
|
/**
|
|
46
49
|
* `<command> <subcommand>` pairs that are exempt while the rest of the group
|
|
@@ -121,6 +121,12 @@ export function getLatestImproveRun(db) {
|
|
|
121
121
|
.prepare(`SELECT ${IMPROVE_RUN_ROW_COLUMNS} FROM improve_runs WHERE dry_run = 0 ORDER BY started_at DESC LIMIT 1`)
|
|
122
122
|
.get();
|
|
123
123
|
}
|
|
124
|
+
/** The `limit` most recent real (non-dry-run) improve runs, newest first (`akm ui`'s run list). */
|
|
125
|
+
export function listRecentImproveRuns(db, limit) {
|
|
126
|
+
return db
|
|
127
|
+
.prepare("SELECT id, started_at, completed_at, ok, scope_mode, scope_value, strategy, result_json FROM improve_runs WHERE dry_run = 0 ORDER BY started_at DESC LIMIT ?")
|
|
128
|
+
.all(limit);
|
|
129
|
+
}
|
|
124
130
|
/**
|
|
125
131
|
* #950: count real (non-dry-run) improve_runs rows whose `started_at` falls
|
|
126
132
|
* in `[since, now)` — same filter as {@link queryImproveRuns}, but a `SELECT
|
|
@@ -17,6 +17,23 @@ export function listUsageEventRows(db, filter) {
|
|
|
17
17
|
ORDER BY datetime(created_at), id`)
|
|
18
18
|
.all(filter.sinceIso, untilBound, ...sourceParams);
|
|
19
19
|
}
|
|
20
|
+
/** One asset's feedback rows (`entry_ref` is the durable `bundle//conceptId`), newest first (`akm ui`'s asset view). */
|
|
21
|
+
export function listFeedbackEventRows(db, entryRef, limit) {
|
|
22
|
+
return db
|
|
23
|
+
.prepare(`SELECT id, event_type, query, entry_id, entry_ref, signal, metadata, source, created_at
|
|
24
|
+
FROM usage_events
|
|
25
|
+
WHERE event_type = 'feedback' AND entry_ref = ?
|
|
26
|
+
ORDER BY datetime(created_at) DESC, id DESC LIMIT ?`)
|
|
27
|
+
.all(entryRef, limit);
|
|
28
|
+
}
|
|
29
|
+
/** Feedback counts by signal for one asset, all sources (`akm ui`'s asset view). */
|
|
30
|
+
export function countFeedbackBySignal(db, entryRef) {
|
|
31
|
+
const rows = db
|
|
32
|
+
.prepare(`SELECT COALESCE(signal, 'none') AS signal, COUNT(*) AS n FROM usage_events
|
|
33
|
+
WHERE event_type = 'feedback' AND entry_ref = ? GROUP BY signal`)
|
|
34
|
+
.all(entryRef);
|
|
35
|
+
return Object.fromEntries(rows.map((row) => [row.signal, row.n]));
|
|
36
|
+
}
|
|
20
37
|
/**
|
|
21
38
|
* `select` events (a show within 60 s of a search that returned the ref) in the
|
|
22
39
|
* window. The events stream stores the ref as the user typed it (often
|
|
@@ -11,7 +11,7 @@ This version adds the policy batch: Policy decisions 1, 3, 4, 5, 7 and 9 are imp
|
|
|
11
11
|
## TLDR
|
|
12
12
|
|
|
13
13
|
- **The improve run** plans from fresh feedback plus a capped proactive lane, works only on the write-target bundle, and only touches assets someone retrieved in the last 90 days or new material. It takes a PID lock, syncs a git-backed bundle like `akm sync` (commit pending managed-path edits, fetch, merge, push) before it reads anything, has a 2-hour budget, commits and pushes only the files it wrote, and records every run in `improve_runs`.
|
|
14
|
-
- **Reflect** runs only on fresh negative feedback (or the proactive lane) and may change only `description`, `when_to_use` and a title,
|
|
14
|
+
- **Reflect** runs only on fresh negative feedback (or the proactive lane) and may change only `description`, `when_to_use` and a title, and the title edits the body only by adding a missing `# heading`; the body is otherwise byte for byte unchanged. The judge must score every criterion 4 or higher, and nothing is checked after it. A non-pass with a mean of 3.5 or more and a judge outage go to a person as a pending proposal; a lower non-pass or a defect is refused with no proposal and a hard 14-day window.
|
|
15
15
|
- **Distill** needs feedback in the last 30 days, and skips a memory whose only feedback is a positive with no reason or note (10 of 11 lessons from such memories were rejected). Lessons that pass the judge still go to a person, never the drain. The proactive lane never feeds distill.
|
|
16
16
|
- **Consolidate** only promotes memories to knowledge; the pair pass retires a duplicate only when the claim lists show nothing would be lost. A retirement is staged for the drain only if the claims are clean, there is no continuity risk and the second look agrees. There is no confidence cutoff. A `captureMode: hot` memory is retired, promoted and archived like any other. The pair pass, the coverage gate and the continuity check all need embeddings, which are off by default.
|
|
17
17
|
- **Triage** accepts only what a deterministic gate passed (a proposal whose judge stamp still matches its content hash, or a retirement the pair pass staged), up to 200 per run. The judgment step is off by default and may only accept consolidate promotions. Proactive-lane proposals, distill lessons and uncertain judge verdicts wait for a person.
|
|
@@ -211,7 +211,7 @@ These 14 bugs are fixed on this branch; the rules below describe the fixed behav
|
|
|
211
211
|
- **Ranking inside the lane** — Priority is the salience rank score computed from type-stub encoding, retrieval frequency, last use and file size. It has no outcome term. Ties break by staleness (never-touched first), then ref ascending. There is no randomisation, so at 15 a day and 30 days between visits at most about 450 distinct assets ever cycle, and among never-retrieved assets of one type priority differs only by file size, so the same smallest files recur. The lane has no memory of futility: a `no_change` reflect records `unchanged` and resets the 30-day clock, and the no-op damper applies only to the merged loop ranking, not to the lane's own priority (`rankScore` is used raw), so an asset reflect already found sound returns at full priority every 30 days. (`proactive-maintenance.ts:94-123`, `preparation.ts:963-966`)
|
|
212
212
|
- **Lane attribution** — Each planned ref is tagged `proactive` < `signal-delta` < `scope`; the strongest lane wins. The tag goes on the dry-run plan, the events, and the proposal as `eligibilitySource`. *Why:* so outcomes can be sliced by lane. (`preparation.ts:948-954`, `src/commands/proposal/proposal-types.ts:15-27`)
|
|
213
213
|
- **Events and plan** — A live run appends a `proactive_selected` event (count, dueTotal, neverReflected). The plan reports `configured`, `effective` (`dueDays`, `maxPerRun`), `candidatePool` and `selectedRefs`. (`preparation.ts:1119-1146`)
|
|
214
|
-
- **What it may propose** — Reflect proposals in practice: reflect
|
|
214
|
+
- **What it may propose** — Reflect proposals in practice: reflect edits frontmatter (`description`, `when_to_use`, `title`), plus at most a missing level-1 heading; the body is otherwise byte for byte unchanged. A proactively picked memory is never distilled, because it has no feedback signal (`memory_distill_requires_feedback`; non-memory picks fail distill's type filter); the only link to distill is that a distill attempt resets the lane's due clock. *Why:* on 396 labelled edits, frontmatter-only fixes were good 24/26 times, and edits that also rewrote the body were bad 175 of 224. (`docs/architecture/improvement.md` "Quality judge", `loop-stages.ts:302-310`)
|
|
215
215
|
- **Its proposals wait for a person** — `proposal drain` never accepts a proposal with `eligibilitySource:"proactive"`, whether a deterministic gate, an earlier staged accept or the judgment tier would take it. It is deferred with reason `proactive-needs-review` and is not sent to the judgment tier. Empty diffs are still rejected. Unattended, then, the lane cannot fix anything: the proposal expires after 90 days while the asset is due again after 30, so it is re-reflected at about day 30 and day 60 before the first proposal expires, and up to three identical pending proposals can exist for one asset (nothing blocks a second pending reflect proposal for a ref; see Reflect: Nothing prevents a second pending reflect proposal). *Why:* with the lane on and the drain promoting, reflect edits on assets with no feedback were accepted unattended. The nightly eval showed `when_to_use`, `type` and provenance stamps and a title heading added to assets no one asked about (#1147). (`src/commands/proposal/drain.ts:12-17,593-596`, CHANGELOG 0.10.26101001 "A proposal from the proactive lane waits for a person", #1147)
|
|
216
216
|
- **Unreviewed proactive proposals expire** — A pending proposal older than `archiveRetentionDays` (default 90; 0 disables) is archived as rejected (`expired`) in post-loop hygiene. Counting starts from the last reopen. Retire proposals expire the same way (fixed in this PR, #10). (`src/commands/proposal/repository.ts:872-927`)
|
|
217
217
|
|
|
@@ -296,7 +296,7 @@ All paths are relative to the `rules-010` worktree. Citation form is `path:line`
|
|
|
296
296
|
|
|
297
297
|
### What reflect may change
|
|
298
298
|
|
|
299
|
-
- **Only `description`, `when_to_use` and a title. The body text is the source's own, except that a title adds a `# title` heading.** The engine returns `confidence` plus `frontmatterPatch {description, when_to_use, title}`, each a non-empty single-line string or `null`. akm applies the patch itself. When the patch carries a title and the body has no level-1 heading, akm
|
|
299
|
+
- **Only `description`, `when_to_use` and a title. The body text is the source's own, byte for byte, except that a title adds a missing `# title` heading.** The engine returns `confidence` plus `frontmatterPatch {description, when_to_use, title}`, each a non-empty single-line string or `null`. akm applies the patch itself. When the patch carries a title and the body has no level-1 heading, akm inserts `# <title>` and one blank line at the body's first non-blank line, in the body's own line ending, and changes nothing else: leading blank lines stay, so the only body difference is the inserted heading. A missing H1 is a valid reflect edit and the only body change reflect may make, so a gate or reviewer that treats a proposal as frontmatter-only must accept it (#1183). Of 21 pending proactive-lane proposals on 2026-10-14, 17 differed from their source only by the inserted heading and the stripped blank lines, and all 17 were rejected as body edits; the title is kept because with the problem list in the prompt the model fixed 189 of 193 missing titles. After the judge, the proposal mint also splices `type:` and `updated:` lines into the frontmatter when they are missing (next rules and Proposal outcome). *Why:* on 396 labelled reflect edits, frontmatter-only fixes were good 24 of 26 times, and edits that also rewrote the body were bad 175 of 224. (`reflect.ts:5-10,229-235,237-259,313-322,428-446`; CHANGELOG 0.9.25-alpha.3)
|
|
300
300
|
- **A title becomes a `# title` heading only if the body has no level-1 heading.** Otherwise the title is ignored. The test is a multi-line regex that also matches a `# comment` line inside a fenced shell block, which suppresses the title. (`reflect.ts:257-259`)
|
|
301
301
|
- **Only the changed keys' frontmatter lines are rewritten.** Every other line is kept, and an unchanged-equal field is dropped from the patch. The mint then adds `type:` and `updated:` lines (see Mint below). *Why:* a source whose YAML the parser reads only partly (a broken description beside a list) must lose nothing. (`reflect.ts:243-245,274,289-304`)
|
|
302
302
|
- **A source whose frontmatter fence is opened but never closed gets no proposal.** It is reported as `no_change` ("empty diff"), recorded `unchanged` and counted by the plasticity counter; the person the comment says should fix it is never told. *Why:* it would leave the old block in the body under a new one, so a person fixes it. (`reflect.ts:253-255,1042-1059`)
|
|
@@ -413,7 +413,6 @@ All paths are relative to the `rules-010` worktree. Citation form is `path:line`
|
|
|
413
413
|
- `salience.ts:129-134` documents the no-op dampener as "Consolidation selection only", but `preparation.ts:956-969` applies it when ranking the reflect/distill loop.
|
|
414
414
|
- The `content_policy_reject` / `reflect-guard-rejected` classification still exists, but the reflect size rail it was named for was removed in 0.9.25-alpha.3.
|
|
415
415
|
- Reflect's judge labels a mean in [2.5, 4) as "review" (`runQualityJudge`), but reflect sends only a mean of 3.5 or more to a person and refuses 2.5 to 3.5; the cut is `REFLECT_REVIEW_MIN_SCORE`, not the judge's label (changed in this PR, decision 4). (`stage.ts:643`, `reflect.ts:1018,1104-1116`)
|
|
416
|
-
- The code comment at `reflect.ts:239-241` and the judge intro (`stage.ts:464`) say the body is kept byte for byte / unchanged apart from an added title heading; neither mentions that every leading blank line of the body is stripped when the heading is prepended (`reflect.ts:258-259`).
|
|
417
416
|
|
|
418
417
|
## Distill
|
|
419
418
|
|
|
@@ -241,7 +241,8 @@ it for a person too. On 2026-10-05 the gate had
|
|
|
241
241
|
staged 12 lessons and 10 were bad (they restated the memory, claimed what it
|
|
242
242
|
does not say, or filed a dated status as a lesson), and no score separated them
|
|
243
243
|
from the two good ones. Reflect revises only an asset's
|
|
244
|
-
`description`, `when_to_use` and title,
|
|
244
|
+
`description`, `when_to_use` and title, and edits the body only to add a missing level-1
|
|
245
|
+
heading; the rest of the body stays byte for byte. On 396 labelled edits,
|
|
245
246
|
those that fixed a frontmatter defect and left the body alone were good 24
|
|
246
247
|
times in 26, and those that also rewrote the body were bad 175 times in 224.
|
|
247
248
|
|
|
@@ -187,7 +187,7 @@ flowchart TD
|
|
|
187
187
|
|
|
188
188
|
`akmReflect` is the agent-invocation subprocess. It always emits a `reflect_invoked` event at entry, regardless of success or failure.
|
|
189
189
|
|
|
190
|
-
Reflect changes only an asset's `description`, `when_to_use` and title. The engine replies with those fields (each `null` when it needs no fix), and akm applies them to the asset it read: the body is kept byte for byte, apart from a `# <title>` heading akm
|
|
190
|
+
Reflect changes only an asset's `description`, `when_to_use` and title. The engine replies with those fields (each `null` when it needs no fix), and akm applies them to the asset it read: the body is kept byte for byte, apart from a `# <title>` heading and one blank line akm inserts when the body has no level-1 heading (the only body change reflect makes). A reply that changes nothing creates no proposal.
|
|
191
191
|
|
|
192
192
|
**Internal steps:**
|
|
193
193
|
|
|
@@ -208,7 +208,7 @@ Reflect changes only an asset's `description`, `when_to_use` and title. The engi
|
|
|
208
208
|
LLM does. An LLM endpoint that rejects JSON Schema gets the framed-markdown
|
|
209
209
|
contract instead.
|
|
210
210
|
6. Validate the reply with `parseSchemaReflectOutput` (`parseFramedReflectOutput` for the framed contract), which strips `<think>` blocks and code fences, then requires exactly the contract's fields: `confidence` and a `frontmatterPatch` of `description`, `when_to_use` and `title` (each a non-empty single-line string, or `null` for no change), plus `ref` when no target was given. A reply that fails gets one repair turn, shared across self-refine passes: the conversation so far, and a request to reformat the reply in the contract (`reflect-output-repair.md`). A reply still invalid fails with `parse_error` and queues nothing. A failed agent or SDK dispatch is reported as it ran, with its exit code and stderr.
|
|
211
|
-
7. Apply the patch (`applyReflectPatch`) to the source asset: its frontmatter with the non-null `description` and `when_to_use` set, and its body unchanged. A non-null `title` becomes a `# <title>` heading and one blank line at the
|
|
211
|
+
7. Apply the patch (`applyReflectPatch`) to the source asset: its frontmatter with the non-null `description` and `when_to_use` set, and its body unchanged. A non-null `title` becomes a `# <title>` heading and one blank line inserted at the body's first non-blank line, in the body's line ending, only when the body has no level-1 heading; otherwise it is ignored. Leading blank lines and every other body byte are kept. A source that requires a `description` and has none gets one derived from its own text when the patch gave none (#636). A patch that changes nothing (every field `null`, or equal to the source's, and no `description` to derive) is the `no_change` outcome (`reflect_skipped_noop`), as is a source with no content to patch; a result that differs only cosmetically is `reflect_skipped_cosmetic`. Neither creates a proposal.
|
|
212
212
|
8. Quality gate (`processes.reflect.qualityGate`, on unless disabled): one judge call scores the revision against the source on **need**, **preservation** and **quality** ([Quality judge](../improvement.md#quality-judge)), and passes it only when every criterion scores 4 or more. A score that does not pass is refused (`quality_rejected`, no proposal) unless its mean is 3.5 or more: that is uncertain, not bad, and the proposal is deferred for a person (`judge-uncertain`, gate `quality-gate`, with the criterion `scores` and the judge's reason) and records no rejection window. A judge that times out, errors or replies unparseably gives no verdict, and the proposal is deferred for review too (`judge-error`, gate `quality-gate`). Before the judge, with the gate on or off, reflect refuses (`quality_rejected`, no proposal, no judge call; the rule is the event's `reflectDefect`) a revision that adds placeholder text (`placeholder_added`: "please confirm", "to be confirmed"; not `TODO`, `TBD` or `FIXME`), talks about its own edit (`meta_commentary_added`: "the feedback says", "this revision") or copies frontmatter into its body (`frontmatter_copied_into_body`: `sources:` or `updated:` lines outside code, a `sources` value). Each rule counts only what the revision adds to its source (`findReflectDefect`), and its wording is a list that `processes.reflect.defectFilter` replaces or, when empty, turns off ([Configuration](../../reference/configuration.md#strategies)).
|
|
213
213
|
9. Write the proposal (`mintProposal`). A pass the judge passed is stamped `staged`, and the triage drain accepts it; with the gate off the proposal is minted unstamped for the drain to decide. One made with no judge configured (`no-judge-configured`) is deferred for review (gate `reflect`), and so is one whose judge gave no verdict (`judge-error`) and one in the judge's uncertain band (`judge-uncertain`). `akm proposal list` shows the reason; the drain leaves every deferral for a person.
|
|
214
214
|
|
package/docs/reference/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Authoritative reference documentation for the akm CLI and its data.
|
|
|
5
5
|
- [Programmatic API](api.md) -- `akm-cli/api`: in-process `curate()` for the akm plugins
|
|
6
6
|
- [Bundle Types](bundle-types.md) -- Supported bundle formats and compatibility
|
|
7
7
|
- [CLI](cli.md) -- All `akm` commands and flags
|
|
8
|
+
- [UI API](ui-api.md) -- The read-only JSON API behind `akm ui`
|
|
8
9
|
- [Configuration](configuration.md) -- Engines, strategies, bundles, and settings
|
|
9
10
|
- [Supported Formats](supported-formats.md) -- Formats akm can index, from its own bundle layout to other tools' existing asset directories
|
|
10
11
|
- [Tasks](tasks.md) -- Task source v4 grammar, executable targets, scheduling, migration, and scheduler operations
|
package/docs/reference/cli.md
CHANGED
|
@@ -490,6 +490,55 @@ Notes you may see:
|
|
|
490
490
|
- Selects are read from the events stream, which records no source, so they are
|
|
491
491
|
counted whatever the usage source is.
|
|
492
492
|
|
|
493
|
+
### ui
|
|
494
|
+
|
|
495
|
+
**Experimental** (see [STABILITY.md](../../STABILITY.md)): the command, the page and the API it reads may change in any
|
|
496
|
+
release.
|
|
497
|
+
|
|
498
|
+
Serve a local web UI over your whole akm environment and open it in your browser. It is read-only except that you can accept and reject proposals in it: health and metrics, bundles
|
|
499
|
+
(with git status and asset counts), the effective config with where each value comes from, the proposal queue with
|
|
500
|
+
diffs, recent changes to the write-target stash, and search with an asset view. Every page reads through the functions
|
|
501
|
+
the matching CLI commands use, so the UI and the CLI report the same numbers.
|
|
502
|
+
|
|
503
|
+
| Page | Shows |
|
|
504
|
+
| --- | --- |
|
|
505
|
+
| Overview | Overall health, failing and warning check counts and the akm version; the health checks grouped by status with expandable evidence; metrics tiles; the last 10 improve runs (result, duration, model calls and failures, sync, triage) with bars; and a 14-day stacked usage chart (search, curate, show, feedback). |
|
|
506
|
+
| Bundles | One card per bundle: kind, path or source, writable, git branch, ahead/behind, last commit time and asset counts by type; plus the index state. |
|
|
507
|
+
| Config | The effective config as a collapsible tree with a `default` / `file` / `extends:<ref>` source badge on every key, the engines table and the `AKM_*` environment. |
|
|
508
|
+
| Proposals | The pending queue grouped by source (filter by source) with age and gate decision, a Recent tab of accepted, rejected and reverted proposals with reasons, and a detail page with the unified diff and, for retirements, the pair judgment. The detail page has Accept (after an inline confirmation) and Reject (with a required reason); the pending list has checkboxes and an Accept selected bar that accepts several in one commit. |
|
|
509
|
+
| Changes | Recent stash commits as a timeline by day, each tagged by origin (`improve-sync`, `accept`, `remember`, `sync`, `other`) with its changed paths. |
|
|
510
|
+
| Search | A query box with type and bundle filters. A result opens the asset view: frontmatter, the body as safe markdown, links, and feedback counts and history. |
|
|
511
|
+
|
|
512
|
+
The pages are plain HTML, CSS and JavaScript modules in `src/assets/ui/`: no framework, no build step, and no request
|
|
513
|
+
beyond the local server. All text from the API is escaped before it reaches the page.
|
|
514
|
+
|
|
515
|
+
```sh
|
|
516
|
+
akm ui
|
|
517
|
+
akm ui --port 8123 --no-open
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
| Flag | Description |
|
|
521
|
+
| --- | --- |
|
|
522
|
+
| `--port <n>` | Port to listen on. Default: a random free port. |
|
|
523
|
+
| `--host <addr>` | Address to bind. Default `127.0.0.1`. Anything else is reachable from other machines and prints a warning. |
|
|
524
|
+
| `--no-open` | Print the URL without opening a browser. A failed open also falls back to printing it. |
|
|
525
|
+
|
|
526
|
+
The command prints `akm ui: http://127.0.0.1:<port>/?token=<token>` and keeps serving until Ctrl-C (SIGINT or SIGTERM).
|
|
527
|
+
Each launch generates a new random token. Every `/api/` request must send it as the `X-Akm-Ui-Token` header or a `token`
|
|
528
|
+
query parameter, otherwise it gets a 401; the page keeps it in `sessionStorage`. The `Host` header must be the address
|
|
529
|
+
the server is bound to, which stops a web page from reaching it through DNS rebinding.
|
|
530
|
+
|
|
531
|
+
Browsing is read-only: `state.db` and `index.db` are opened read-only and never created or migrated, searching and
|
|
532
|
+
showing in the UI record no usage events (so browsing is never counted as demand) and build no index, and nothing is
|
|
533
|
+
written to a bundle or to config. The only writes are accepting and rejecting proposals (and accepting several at once),
|
|
534
|
+
through three `POST` routes that call the functions `akm proposal accept` and `reject` call, so a decision made in the
|
|
535
|
+
UI is recorded and committed exactly like one made on the command line. Those routes need the token in the
|
|
536
|
+
`X-Akm-Ui-Token` header (not the query string), a JSON content type and an `Origin` equal to the server's own address.
|
|
537
|
+
A read is short and the server holds no lock between requests, so browsing never blocks `akm improve`, `akm index` or
|
|
538
|
+
`akm sync`; a decision takes the same write lease the CLI takes and answers `409` at once if another process holds it. Secrets are redacted: config values under secret-like keys and URL credentials
|
|
539
|
+
are masked, and env and secret assets are shown as a stub, never with their values. The endpoints are documented in
|
|
540
|
+
[ui-api.md](ui-api.md). `--format` has no effect on this command.
|
|
541
|
+
|
|
493
542
|
### search
|
|
494
543
|
|
|
495
544
|
Search bundle assets, registries, or both.
|