create-pmos 0.96.0 → 0.97.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.96.0",
3
+ "version": "0.97.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@a11a7a5
1
+ kernel-source: pmos@b496b6e
2
2
  assembled: 2026-10-07
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-07T23:35:56Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T23:54:43Z"}
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-07T23:35:56Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T23:54:43Z"}
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-07T23:35:56Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-07T23:54:43Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -29,7 +29,7 @@ On a desktop the workspace is one frame. [Layout](layout.md) explains each part.
29
29
  | Run reports | Reports |
30
30
  | Whether a document is the same as its Google Docs or Confluence copy | Docs: ⌘K → **Docs**, or press `g d` |
31
31
  | The people you work with: who they are, your interactions, their tone, your notes and shared work | People: ⌘K → **People**, or press `g p` |
32
- | Repositories, Profiles, New sessions (the default CLI and model), Validate consent, People (calendar names), Theme | Settings |
32
+ | Repositories, Profiles, New sessions (the default CLI, model, Claude profile and launch flags), Validate consent, People (calendar names), Theme | Settings |
33
33
  | Validate | Settings, under Validate consent: **Open Validate** |
34
34
  | A document, a decision record, a memory entry, a page of this guide | ⌘K, with its scope: Knowledge, Memory or Guide |
35
35
  | A report across every repository | ⌘K: Portfolio report |
@@ -16,7 +16,7 @@ Corners are rounded a little: 6 pixels on buttons and fields, 8 pixels on cards
16
16
  The top bar has these parts, from left to right:
17
17
 
18
18
  - **Repository ▾** names the current repository. Press it to choose another repository, or **All repositories** to see the work of every repository at once. All repositories replaces the old Portfolio mode.
19
- - **Branches and sessions** is in ⌘K. It opens a sheet with every branch of the repository (main first) and how many sessions run in each. Press a branch to see its sessions, open one, end one, or start a session there. **+ branch** makes a branch, and **+ session** starts one with a branch, an agent and a profile. With All repositories, the sheet lists every repository.
19
+ - **Branches and sessions** is in ⌘K. It opens a sheet with every branch of the repository (main first) and how many sessions run in each. Press a branch to see its sessions, open one, end one, or start a session there. **+ branch** makes a branch, and **+ session** starts one with a branch, an agent, a profile and launch flags. The form starts from the defaults in Settings, under New sessions. With All repositories, the sheet lists every repository.
20
20
  - **Agenda, My work, Initiatives, Board, Reports, Docs and People** are the places. Each has an icon before its name, the same icon as on the phone. Press one to show it. The Agenda count shows how many items need you. Docs shows whether each document is the same as its Google Docs and Confluence copy. People shows the people you work with in the repository ([People](people.md)).
21
21
  - **Search or capture ⌘K** opens the command palette. Type to find an initiative, a repository, an open tab or a command.
22
22
  - **Sync** shows only when the current repository is not in sync with its remote. Press it to fetch, bring in the work of the other machine, record your changes and push. If the repository has not fetched in the last 10 minutes, Sync also shows, because a push from another machine is not visible until a fetch. When the repository fetched recently and is in sync, Sync is not shown. When two machines add lines to the same ledger (a `.jsonl` file under `pmos/` or `planning/`), Sync keeps the lines from both machines. If Sync stops, the message names the step that stopped. If both machines changed the same lines of another file, Sync changes nothing and names the files. Then choose **Push this machine’s version** or **Pull the other machine’s version**. Sync keeps that version of those files, keeps all other changes from both machines, and finishes. It never overwrites the remote history.
@@ -30,8 +30,8 @@ Each place is drawn when its run is built. Until then the place says which run b
30
30
  Settings holds settings only, in eight sections. On a wide screen they are cards in two columns, like Agenda: Repositories, New sessions, Profiles and Theme on the left, and Validate consent, People, Online and Updates on the right. On a narrow screen the cards stack in one column. Each switch (Validate consent, People, Online) turns on when you press it. **Check for updates** is at the top right of the page.
31
31
 
32
32
  - **Repositories**: every registered repository. Press a repository to make it the current one. Press its ⋯ for **Rename** or **Remove from PMOS**. Each asks in a sheet, and a remove asks you to confirm. **Add repository** shows the scan of a folder before you add it.
33
- - **Profiles**: the Claude profiles a session can start with. They open in the detail.
34
- - **New sessions**: the default CLI and the default model for a new session in the dock.
33
+ - **Profiles**: the Claude profiles a session can start with. Press **New profile** to make one. This writes `~/.claude/profiles/<id>.json`. Account secrets are never shown in the browser.
34
+ - **New sessions**: the default CLI, the default model, the default Claude profile and the default launch flags for a new session. The flags come from `claude --help`, for example `--chrome`. A flag that needs a value lists the values it accepts.
35
35
  - **Validate consent**: the consent of the current repository. Use its switch to turn it on or off. **Open Validate** opens the Validate page as a tab.
36
36
  - **People**: whether the morning brief keeps attendee names from the calendar for the current repository. It is off until you turn it on. Turning it off also hides the names recorded before.
37
37
  - **Online**: puts this workspace at your own address, such as `pmos.pm-os.app`, through a Cloudflare tunnel. Enter the public address, the tunnel name, your Cloudflare team domain, the Access audience (AUD) tag and your email, then turn on the **Online** switch. The workspace runs the tunnel and remembers the switch when it restarts. On your phone, open the address and sign in with Cloudflare Access (Google or an email code). The workspace does not ask for its token there. Only the computer that runs the workspace can change this section. A phone sees whether it is on. When a sign-in at the address is refused, this section names the check that failed, such as the email or the AUD tag. [REMOTE.md](../REMOTE.md) has the one-time Cloudflare steps.
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+ /*
3
+ * claude-flags — the launch flags the workspace will pass to a `claude` session (initiative
4
+ * `workspace-launch-flags`).
5
+ *
6
+ * The list is DATA: `claude-flags.json`, generated from `claude --help` by scripts/gen-claude-flags.js.
7
+ * It serves two jobs. The new-session form reads it to show every flag the CLI supports, and the server
8
+ * reads it to decide what a client may ask for. A flag the file does not name is refused, so the form
9
+ * cannot be used to put arbitrary text on a command line.
10
+ *
11
+ * A request is a list of `{ flag, value? }`. `resolve()` turns it into launch tokens, or says why not. It
12
+ * is pure: it starts nothing and reads only the data file.
13
+ *
14
+ * · `--name`, `--model`, `--worktree` and the like are listed but REFUSED: PMOS answers those questions
15
+ * itself, and a session must not get two answers to one of them.
16
+ * · a flag that only works with --print is refused: a workspace session is interactive.
17
+ * · values travel as `--flag=value`, one token, so a value that starts with `-` cannot be read as a flag.
18
+ */
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ const FILE = path.join(__dirname, 'claude-flags.json');
23
+ const MAX_VALUE = 500;
24
+
25
+ let cache = null;
26
+ function data() {
27
+ if (cache) return cache;
28
+ try {
29
+ const d = JSON.parse(fs.readFileSync(FILE, 'utf8'));
30
+ cache = { ok: true, cli_version: d.cli_version || null, flags: Array.isArray(d.flags) ? d.flags : [] };
31
+ } catch (e) {
32
+ cache = { ok: false, error: `the claude flag list ${FILE} could not be read — ${String((e && e.message) || e).slice(0, 120)}`, flags: [] };
33
+ }
34
+ return cache;
35
+ }
36
+
37
+ /** The whole list, for the form. Each entry also says whether it can be chosen and, if not, why. */
38
+ function list() {
39
+ const d = data();
40
+ return {
41
+ ok: d.ok, error: d.error || null, cli_version: d.cli_version || null,
42
+ flags: d.flags.map((f) => ({
43
+ ...f,
44
+ offered: !f.managed && !f.printOnly,
45
+ why_not: f.managed ? `PMOS handles this itself — ${f.managed}` : f.printOnly ? 'only works with --print, and a workspace session is interactive' : null,
46
+ })),
47
+ };
48
+ }
49
+
50
+ const byName = (flags, name) => flags.find((f) => f.flag === name || (f.aliases || []).includes(name));
51
+
52
+ /** `'` quoting for the tmux path, which hands the command to a shell. */
53
+ const shellQuote = (s) => (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s) ? s : `'${String(s).replace(/'/g, `'\\''`)}'`);
54
+
55
+ /**
56
+ * @param {Array<{flag:string,value?:string}>|null|undefined} requested
57
+ * @param {string} agent the agent binary the session runs
58
+ * @returns {{ok:true, args:string[], applied:Array<{flag:string,value:string|null}>} | {ok:false, error:string}}
59
+ */
60
+ function resolve(requested, agent) {
61
+ if (requested == null || (Array.isArray(requested) && requested.length === 0)) return { ok: true, args: [], applied: [] };
62
+ if (!Array.isArray(requested)) return { ok: false, error: 'launch flags must be a list' };
63
+ if (agent !== 'claude') return { ok: false, error: `launch flags are only offered for a claude session, and this one runs ${agent === 'shell' ? 'a shell' : agent}` };
64
+ const d = data();
65
+ if (!d.ok) return { ok: false, error: d.error };
66
+ const args = [];
67
+ const applied = [];
68
+ const seen = new Set();
69
+ for (const r of requested) {
70
+ const name = r && typeof r.flag === 'string' ? r.flag.trim() : '';
71
+ const f = name ? byName(d.flags, name) : null;
72
+ if (!f) return { ok: false, error: `launch flag “${name.slice(0, 60) || '(empty)'}” is not a claude flag PMOS knows` };
73
+ if (f.managed) return { ok: false, error: `launch flag ${f.flag} is not accepted here — PMOS handles this itself: ${f.managed}` };
74
+ if (f.printOnly) return { ok: false, error: `launch flag ${f.flag} only works with --print, and a workspace session is interactive` };
75
+ if (seen.has(f.flag)) return { ok: false, error: `launch flag ${f.flag} was given twice` };
76
+ seen.add(f.flag);
77
+ const raw = r.value == null ? '' : String(r.value);
78
+ const value = raw.trim();
79
+ if (f.value === 'none' && value) return { ok: false, error: `launch flag ${f.flag} takes no value` };
80
+ if (f.value === 'required' && !value) return { ok: false, error: `launch flag ${f.flag} needs a value ${f.arg || ''}`.trim() };
81
+ if (value.length > MAX_VALUE) return { ok: false, error: `the value for ${f.flag} is longer than ${MAX_VALUE} characters` };
82
+ if (/[\u0000-\u001f\u007f]/.test(value)) return { ok: false, error: `the value for ${f.flag} holds a control character` };
83
+ if (value && Array.isArray(f.choices) && f.choices.length && !f.choices.includes(value)) {
84
+ return { ok: false, error: `the value for ${f.flag} must be one of: ${f.choices.join(', ')}` };
85
+ }
86
+ args.push(value ? `${f.flag}=${value}` : f.flag);
87
+ applied.push({ flag: f.flag, value: value || null });
88
+ }
89
+ // --chrome with --no-chrome is two answers to one question.
90
+ for (const f of applied) {
91
+ const m = /^--no-(.+)$/.exec(f.flag);
92
+ if (m && seen.has('--' + m[1])) return { ok: false, error: `launch flags --${m[1]} and ${f.flag} contradict each other` };
93
+ }
94
+ return { ok: true, args, applied };
95
+ }
96
+
97
+ /** The sentence for the pane notice and the session list. */
98
+ function line(applied) {
99
+ if (!Array.isArray(applied)) return 'launch flags: not recorded';
100
+ if (!applied.length) return 'launch flags: none';
101
+ return 'launch flags: ' + applied.map((a) => (a.value ? `${a.flag}=${a.value}` : a.flag)).join(' ');
102
+ }
103
+
104
+ module.exports = { list, resolve, line, shellQuote, FILE };