create-pmos 0.165.0 → 0.167.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.165.0",
3
+ "version": "0.167.0",
4
4
  "description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@574eff4
1
+ kernel-source: pmos@23fe058
2
2
  assembled: 2026-10-12
3
3
  mode: vendor
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Eval calibration log"
4
4
  description: "Append-only record of every PM override of an evaluator verdict."
5
- generated: {by: process:create-pmos, at: "2026-10-12T13:19:10Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-12T14:00:51Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Health budgets"
4
4
  description: "Closed registry of health-budget anchors (D59), edited only by the PM."
5
- generated: {by: process:create-pmos, at: "2026-10-12T13:19:10Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-12T14:00:51Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Discoveries and friction log"
4
4
  description: "Append-only record of what runs discovered and where the process rubbed."
5
- generated: {by: process:create-pmos, at: "2026-10-12T13:19:10Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-12T14:00:51Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -284,6 +284,7 @@ To take notes for a calendar event, open the event's detail on the Agenda and pr
284
284
  - In the settings, **Mark private** stops listening at once and deletes the note's audio. A private note is not recorded again, and nothing from it goes to Jev or to an agent. **Mark not private** undoes it.
285
285
  - **Summary by agent** starts an agent session in the selected repository that writes a summary into the note. Lines that look like a secret are removed first.
286
286
  - **Delete note** in the **⋯** menu, or **Delete** on a row of **All notes**, removes the note, its transcript, its summaries and its audio from this machine after you confirm. The note's summary goes from the calendar and the initiative. You cannot undo it. Deleting a note that is not being taken does not stop the one that is.
287
+ - On the console3 page, open the note in **Calendar › Notes** and press the bin next to **Copy as Markdown** in its head. PMOS asks first: **Delete note** deletes it, **Keep it** keeps it. A note that is being recorded stops without keeping its audio, then it is deleted. The next note in the list opens.
287
288
 
288
289
  ## What stays on your machine
289
290
 
@@ -112,7 +112,7 @@ The workspace reads the TypeSafe key only on this machine. After you save the ke
112
112
  If you have no TypeSafe key, a model on your computer can answer the same questions. Two models are supported. Both use the same interface as Jev.
113
113
 
114
114
  - **Winnow**: in a `winnow-inference` folder, run `python3 scripts/serve.py`. It uses port 8091. Winnow-12B needs about 16 GB of memory.
115
- - **Laya**: run `pip install "laya[serve]"`, then `laya-serve`. It uses port 8000. Laya is small and runs on a laptop.
115
+ - **Laya**: in Settings, press **Set up Laya**. A terminal named Set up Laya opens with the commands typed in. Read them and press Enter. The first time, they install Laya into the PMOS data folder. Then Laya starts and uses port 8000. Laya is small and runs on a laptop. Laya stops when you close that terminal or restart the computer. Press **Set up Laya** again to start it.
116
116
 
117
117
  1. Start the model.
118
118
  2. Open Settings, then **Privacy and data**. Under **Local model if Jev is missing**, press **Winnow** or **Laya**.
@@ -90,6 +90,20 @@ function status(stateDir, env = null) {
90
90
  return { present: false, source: null, local: null };
91
91
  }
92
92
 
93
+ /** The commands that install Laya into its own folder under the runtime directory (once) and start it on loopback.
94
+ * The workspace types them into a terminal and never runs them itself: the PM reads them and presses Enter (as D91's
95
+ * Whisper setup does). A folder of its own avoids the system Python, which newer Macs refuse to install into. */
96
+ function layaSetupCommand(stateDir, platform = process.platform) {
97
+ const dir = path.join(stateDir, 'laya');
98
+ const port = typesafe.BACKENDS.laya.port;
99
+ if (platform === 'win32') {
100
+ return `if (-not (Test-Path "${dir}\\Scripts\\laya-serve.exe")) { python -m venv "${dir}"; & "${dir}\\Scripts\\python" -m pip install "laya[serve]" }; `
101
+ + `$env:LAYA_HOST="127.0.0.1"; $env:LAYA_PORT="${port}"; $env:LAYA_DEVICE="cpu"; $env:LAYA_PRELOAD="1"; & "${dir}\\Scripts\\laya-serve"`;
102
+ }
103
+ return `[ -x "${dir}/bin/laya-serve" ] || { python3 -m venv "${dir}" && "${dir}/bin/python" -m pip install "laya[serve]"; } && `
104
+ + `LAYA_HOST=127.0.0.1 LAYA_PORT=${port} LAYA_DEVICE=cpu LAYA_PRELOAD=1 "${dir}/bin/laya-serve"`;
105
+ }
106
+
93
107
  /** Save or clear the local fallback server. `backend` is 'winnow' or 'laya'; `port` defaults to that server's own. */
94
108
  function setLocal(stateDir, { backend, port } = {}) {
95
109
  const cur = read(stateDir);
@@ -163,4 +177,4 @@ function isExcluded(exclude, rel) {
163
177
  });
164
178
  }
165
179
 
166
- module.exports = { adoptEnv, status, getKey, setKey, clearKey, setLocal, getRepo, setRepo, isExcluded, ENV_NAME, FILE };
180
+ module.exports = { adoptEnv, status, getKey, setKey, clearKey, setLocal, layaSetupCommand, getRepo, setRepo, isExcluded, ENV_NAME, FILE };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.165.0",
3
+ "version": "0.167.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.165.0",
9
+ "version": "0.167.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@xterm/addon-fit": "^0.11.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.165.0",
3
+ "version": "0.167.0",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",
@@ -8,6 +8,7 @@
8
8
  * tag, recording or paused, "no meeting", and a dot for a conflict.
9
9
  * the head the note's meeting, then what it is about (the Portfolio, a board, or an initiative on its board) with one picker
10
10
  * (aboutMenu: /api/boards and each board's Now and next initiatives), the mockup the PM approved on 2026-10-12.
11
+ * Copy as Markdown, and Delete (asked first; remove), as the main app's Notes has.
11
12
  * the note the Calendar stage's layout (calendar-views.js stageNode, the mockup the PM approved on 2026-10-12): a head with
12
13
  * Before, During and After, then the stage's cards.
13
14
  * Before the meeting context the note kept (meeting.context): agenda (an item heard is ticked), what it is for, to be ready,
@@ -35,6 +36,7 @@ import * as dock from './note-dock.js';
35
36
  import * as V from './view-notes.js';
36
37
  import { makeTask } from './notes-task.js';
37
38
  import { screenStrip } from './notes-screen-view.js';
39
+ import { askConfirm } from './dialogs.js';
38
40
 
39
41
  const PHASES = [['before', 'clip', 'Before'], ['during', 'mic', 'During'], ['after', 'notes', 'After']];
40
42
  const startOf = (n) => (n.meeting && n.meeting.start) || n.recorded_from || n.created_at || n.updated_at;
@@ -199,7 +201,8 @@ function noteCol(s) {
199
201
  PHASES.map(([k, ic, l]) => btn('sgb', { 'data-cal3-phase': k, 'aria-pressed': String(ph === k), title: l }, [c3ic(ic), el('span', null, l)], () => { s.phase = k; draw(s); })))),
200
202
  el('div', { class: 'chips2 dim cal3-sm' },
201
203
  [dayOf(startOf(n)), len].filter(Boolean).join(' · '),
202
- btn('ib sm', { title: 'Copy as Markdown (with the meeting context)', 'aria-label': 'Copy as Markdown', 'data-cal3-notes': 'copy' }, c3ic('clip'), () => copy(n))),
204
+ btn('ib sm', { title: 'Copy as Markdown (with the meeting context)', 'aria-label': 'Copy as Markdown', 'data-cal3-notes': 'copy' }, c3ic('clip'), () => copy(n)),
205
+ btn('ib sm', { title: 'Delete this note', 'aria-label': 'Delete note', 'data-cal3-notes': 'delete' }, c3ic('trash'), () => remove(s, n))),
203
206
  links(s, n, ev));
204
207
  const body = ph === 'before' ? before(s, n) : ph === 'during' ? during(s, n, mode) : after(s, n, mode);
205
208
  return el('div', { class: 'cal3-ncol', 'data-cal3-notes': 'note', 'data-cal3-note': n.id, 'data-cal3-stage': ph }, head, ...[].concat(body));
@@ -444,6 +447,21 @@ async function copy(n) {
444
447
  catch { toast('The clipboard was not available.'); }
445
448
  }
446
449
 
450
+ /* Delete: asked first, as the main app's Notes asks (view-notes.js deleteNote). A note being recorded stops without keeping its
451
+ * audio, then the note, its transcript, audio and screenshots go (POST /api/notes/delete). The next note in the list opens. */
452
+ async function remove(s, n) {
453
+ const name = n.title || 'Untitled note';
454
+ const sure = await askConfirm({ title: 'Delete "' + name + '"?', body: 'The note, its transcript, its summaries and its kept audio are deleted from this machine. Its summary goes from the calendar and the initiative. This cannot be undone.', confirmLabel: 'Delete note', cancelLabel: 'Keep it', danger: true });
455
+ if (!sure) return;
456
+ if (!(await V.discardRecording({ state: {}, setState: () => {} }, n.id))) { toast('"' + name + '" was not deleted.'); return; }
457
+ dock.forget(n.id);
458
+ s.rows = (s.rows || []).filter((x) => x.id !== n.id);
459
+ s.open = null; s.note = null; s.phase = null; s.shots = []; s.rec = null; s.audio = null;
460
+ toast('"' + name + '" was deleted.');
461
+ refreshNotes();
462
+ read(s);
463
+ }
464
+
447
465
  /* "Show in the calendar": the Calendar switch back to the grid, on the meeting's day, with the meeting selected */
448
466
  function showMeeting(s, ev) {
449
467
  const ui = s.ui; if (!ui) return;
@@ -618,14 +618,24 @@ export function mountSettings(host, ctx = {}) {
618
618
  M.localTest = r && r.ok ? { ok: true, text: 'Answered (' + (r.model || r.local) + ').' } : { ok: false, text: (r && r.error) || 'No answer.' };
619
619
  draw();
620
620
  };
621
+ const setup = async () => {
622
+ M.localTest = { busy: true }; draw();
623
+ const r = await pmPost('/api/typesafe/local/setup', {});
624
+ if (r && r.ok) {
625
+ M.typesafe = r.key; delete M.err['typesafe.local'];
626
+ M.localTest = { ok: !!r.started, text: r.started ? 'A terminal named Set up Laya opened with the commands typed in. Press Enter there, wait until Laya says it is running, then press Test.' : 'No terminal opened (' + (r.reason || 'unknown') + '). Run this in a terminal: ' + r.command };
627
+ } else { M.localTest = null; M.err['typesafe.local'] = (r && r.error) || 'Not started.'; }
628
+ draw();
629
+ };
621
630
  const lt = M.localTest;
622
631
  const note = !loc ? 'Off. With no TypeSafe key, Jev features stay off.'
623
632
  : (t.source === 'local' ? 'No TypeSafe key, so ' + loc.name + ' answers every Jev question.' : 'TypeSafe answers first; ' + loc.name + ' answers when it fails.')
624
633
  + ' PMOS calls 127.0.0.1:' + loc.port + ' only. Nothing leaves this computer.' + (lt && !lt.busy ? ' ' + lt.text : '');
625
- return frame({ key: 'typesafe.local', label: 'Local model if Jev is missing', help: 'Start Winnow (winnow-inference, port 8091) or Laya (laya-serve, port 8000) on this computer, then pick it here.', scope: 'computer',
634
+ return frame({ key: 'typesafe.local', label: 'Local model if Jev is missing', help: 'Press Set up Laya to install and start Laya in a terminal, or start Winnow (winnow-inference, port 8091) yourself and pick it here.', scope: 'computer',
626
635
  err: M.err['typesafe.local'], note,
627
636
  control: t ? el('div', { class: 'iacts' }, seg('Local model', [['off', 'Off'], ['winnow', 'Winnow'], ['laya', 'Laya']], cur, pick),
628
- loc ? btn(lt && lt.busy ? 'Testing' : 'Test', { variant: 'ghost', disabled: !!(lt && lt.busy), onclick: test }) : null) : tag('Reading') });
637
+ btn('Set up Laya', { variant: 'ghost', disabled: !!(lt && lt.busy), onclick: setup, attrs: { 'data-c3set-laya-setup': '' } }),
638
+ loc ? btn(lt && lt.busy ? 'Working' : 'Test', { variant: 'ghost', disabled: !!(lt && lt.busy), onclick: test }) : null) : tag('Reading') });
629
639
  }
630
640
 
631
641
  function appearanceSec() {
@@ -7976,6 +7976,17 @@ async function handleRequest(req, res) {
7976
7976
  if (!out.ok) return json(res, { ok: false, error: out.error }, /choose|port/.test(out.error) ? 400 : 500);
7977
7977
  return json(res, { ok: true, key: tsSettings.status(STATE) });
7978
7978
  }
7979
+ /* Set up Laya: picks Laya as the local model and opens a terminal with the install-and-start commands typed in, not run.
7980
+ * The PM reads them and presses Enter; Laya then runs in that terminal on 127.0.0.1:8000 (D95). */
7981
+ if (p === '/api/typesafe/local/setup' && req.method === 'POST') {
7982
+ const saved = tsSettings.setLocal(STATE, { backend: 'laya' });
7983
+ if (!saved.ok) return json(res, { ok: false, error: saved.error }, 500);
7984
+ const command = tsSettings.layaSetupCommand(STATE);
7985
+ const home = homeWorkspace();
7986
+ const s = spawnSession({ name: 'Set up Laya', agent: 'shell', ws: home ? home.id : undefined, origin: 'laya-setup', initialInput: command });
7987
+ const started = !!s && s.state !== 'exited';
7988
+ return json(res, { ok: true, started, session: s ? s.id : null, command, key: tsSettings.status(STATE), reason: started ? '' : ((s && s.note) || 'could not start a terminal') });
7989
+ }
7979
7990
  if (p === '/api/typesafe/local/test' && req.method === 'POST') {
7980
7991
  if (!tsSettings.status(STATE).local) return json(res, { ok: false, error: 'No local model is set.' }, 409);
7981
7992
  const r = await tsClient.ask({ key: tsClient.LOCAL_KEY, state: 'PMOS is checking that the local model answers.', retries: 0,