create-pmos 0.243.0 → 0.245.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 +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/c3-settings-test.js +3 -0
- package/workspace/guide/layout.md +1 -0
- package/workspace/lib/headless.js +9 -2
- package/workspace/lib/meeting-focus.js +6 -4
- package/workspace/lib/own-profile.js +37 -0
- package/workspace/lib/routes-meeting-focus.js +1 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-settings.js +26 -0
- package/workspace/server.js +9 -0
- package/workspace/test-files.json +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.245.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": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-15T03:51:47Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-15T03:51:47Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-15T03:51:47Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -73,6 +73,9 @@ async function waitFor(page, fn, arg, ms = 8000) {
|
|
|
73
73
|
ok(`B2 section ${id} draws`, drew);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
await page.click('[data-c3set-nav] [data-c3set-sec="sessions"]');
|
|
77
|
+
ok('B3 Claude sessions has the PMOS own-login row with Set up', await waitFor(page, () => { const r = document.querySelector('[data-c3set-section="sessions"] [data-c3set-own]'); const b = r && r.querySelector('[data-c3set-own-signin]'); return !!b && /Set up/.test(b.textContent) && !r.querySelector('[data-c3set-own-use]'); }, null, 4000));
|
|
78
|
+
|
|
76
79
|
/* ── C: search ── */
|
|
77
80
|
await page.fill('[data-c3set-search]', 'language');
|
|
78
81
|
ok('C1 search finds the recording language', await waitFor(page, () => !!document.querySelector('[data-c3set-body] [data-c3set-row="notes.language"]')));
|
|
@@ -33,6 +33,7 @@ Settings holds settings only, in eight sections. On a wide screen they are cards
|
|
|
33
33
|
|
|
34
34
|
- **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.
|
|
35
35
|
- **Profiles**: the Claude profiles a session can start with. These are the descriptors in `~/.claude/profiles`, and also the profiles you already run: a shell command that sets `CLAUDE_CONFIG_DIR` (for example `alias claude-work='CLAUDE_CONFIG_DIR=~/.claude-work claude'`), a launcher script named `claude-*`, or a config folder such as `~/.claude-work`. Each profile is a row with a **Detected** badge when PMOS found it rather than wrote it, and a **Default** badge when it is the default. Its ⋯ menu has **Use as default**. A profile found this way says where it was found, and a session under it sets only `CLAUDE_CONFIG_DIR`. The list reads again every few seconds while Settings is open, and **Look again** in the Claude profiles menu reads it at once. When nothing is found, the note names the start-up files and folders PMOS looked at. Press **New profile** to make one. This writes `~/.claude/profiles/<id>.json`. Account secrets are never shown in the browser.
|
|
36
|
+
- **PMOS's own Claude login** (Claude sessions): a Claude login that only PMOS uses. A `/login` typed in a terminal replaces the account in `~/.claude`, and PMOS's background runs then fail with "OAuth session expired". Press **Set up**. PMOS makes the profile `pmos` (the folder `~/.claude-pmos`) and opens a Claude session under it. Type `/login` there and sign in once. Then press **Use for PMOS**. The row shows **In use**. Terminals keep working as before: sign in or switch there whenever you like, and PMOS's login is not touched. **Sign in again** opens the session again. PMOS never makes this the default by itself, and it holds no password.
|
|
36
37
|
- **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. The default profile is one setting for the whole workspace. PMOS keeps it on the server, so every browser, port and device starts a Claude session with the same profile. Choose it here or with "make … the default" in the New session form. Both change the same setting.
|
|
37
38
|
- **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.
|
|
38
39
|
- **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.
|
|
@@ -217,6 +217,11 @@ function parseResult(stdout) {
|
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/** Did the run fail because Claude is signed out on this computer? The CLI prints "Failed to authenticate: OAuth session expired…" and exits 1. */
|
|
221
|
+
const SIGNED_OUT_RE = /failed to authenticate|oauth (?:session|token) (?:has )?expired|could not be refreshed|please run \/login|not logged in|invalid (?:api key|bearer token)|authentication_error/i;
|
|
222
|
+
const signedOut = (text) => SIGNED_OUT_RE.test(String(text || ''));
|
|
223
|
+
const SIGNED_OUT_WHY = 'Claude is signed out on this computer. Open Terminal, run `claude`, type /login and sign in. PMOS carries on by itself afterwards.';
|
|
224
|
+
|
|
220
225
|
const oneLine = (v, max) => { const t = String(v == null ? '' : v).replace(/\s+/g, ' ').trim(); return t.length > max ? t.slice(0, max - 1) + '…' : t; };
|
|
221
226
|
// The one argument that says what a tool call does: the command, the file, the pattern, the address.
|
|
222
227
|
function toolWhat(input) {
|
|
@@ -260,17 +265,19 @@ function outcomeOf(r, { code = null, signal = null, timedOut = false, stopped =
|
|
|
260
265
|
const first = String(res.text || '').split('\n').map((x) => x.replace(/^[#>*\-\s]+/, '').trim()).find(Boolean) || '';
|
|
261
266
|
const denied = Array.isArray(res.denied) ? res.denied : [];
|
|
262
267
|
const base = { turns: res.turns == null ? null : res.turns, durationMs: res.durationMs == null ? null : res.durationMs, denied };
|
|
268
|
+
const auth = !timedOut && !stopped && signedOut(res.text);
|
|
263
269
|
const ok = !timedOut && !stopped && !signal && code === 0 && res.ok && !res.isError;
|
|
264
270
|
if (ok) return { ok: true, headline: oneLine(first || 'Finished. It wrote no summary.', 160), why: null, ...base };
|
|
265
271
|
let why;
|
|
266
272
|
if (stopped) why = 'Stopped by you.';
|
|
267
273
|
else if (timedOut) why = `Stopped: still running after ${timeoutMin || Math.round(DEFAULT_TIMEOUT_MS / 60000)} minutes.`;
|
|
268
274
|
else if (signal) why = `The process was ended (${signal}).`;
|
|
275
|
+
else if (signedOut(res.text)) why = SIGNED_OUT_WHY;
|
|
269
276
|
else if (!res.ok) why = `It exited with code ${code} and printed no result${res.text ? ': ' + oneLine(res.text.split('\n').filter(Boolean).pop(), 200) : '.'}`;
|
|
270
277
|
else if (res.isError) why = `The run reported an error${first ? ': ' + oneLine(first, 200) : '.'}`;
|
|
271
278
|
else why = `It exited with code ${code}.`;
|
|
272
279
|
if (denied.length) why += ` Not allowed in a background run: ${denied.join(', ')}.`;
|
|
273
|
-
return { ok: false, headline: null, why, ...base };
|
|
280
|
+
return { ok: false, headline: null, why, ...(auth ? { auth: true } : {}), ...base };
|
|
274
281
|
}
|
|
275
282
|
|
|
276
283
|
function logLines(r, exitCode) {
|
|
@@ -284,4 +291,4 @@ function logLines(r, exitCode) {
|
|
|
284
291
|
return out;
|
|
285
292
|
}
|
|
286
293
|
|
|
287
|
-
module.exports = { HEADLESS_SKILLS, HEADLESS_ORIGINS, ASK_ORIGINS, DRIVE_ORIGINS, SCAN_WEB_ORIGINS, SCAN_REPO_ORIGINS, SCAN_RESEARCH_WEB_ORIGINS, SCAN_RESEARCH_REPO_ORIGINS, WEB_KINDS, REPORT_SETUP_ORIGINS, REPORT_FIND_ORIGINS, CORE_DOC_ORIGINS, REPORT_DESIGN_ORIGINS, REPORT_SAMPLE_FETCH_ORIGINS, REPORT_STRUCTURE_ORIGINS, REPORT_STRUCTURE_DRY_ORIGINS, TOOLS, DEFAULT_TIMEOUT_MS, kindFor, argsFor, timeoutMs, parseResult, streamLines, outcomeOf, logLines, switchedOff };
|
|
294
|
+
module.exports = { HEADLESS_SKILLS, HEADLESS_ORIGINS, ASK_ORIGINS, DRIVE_ORIGINS, SCAN_WEB_ORIGINS, SCAN_REPO_ORIGINS, SCAN_RESEARCH_WEB_ORIGINS, SCAN_RESEARCH_REPO_ORIGINS, WEB_KINDS, REPORT_SETUP_ORIGINS, REPORT_FIND_ORIGINS, CORE_DOC_ORIGINS, REPORT_DESIGN_ORIGINS, REPORT_SAMPLE_FETCH_ORIGINS, REPORT_STRUCTURE_ORIGINS, REPORT_STRUCTURE_DRY_ORIGINS, TOOLS, DEFAULT_TIMEOUT_MS, kindFor, argsFor, timeoutMs, parseResult, streamLines, outcomeOf, signedOut, SIGNED_OUT_WHY, logLines, switchedOff };
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
const CATS = Object.freeze(['Answer', 'Fact check', 'Decision', 'Time', 'Topics', 'Context', 'Prep', 'Follow-up']);
|
|
25
25
|
const REF_TYPES = Object.freeze(['initiative', 'plan-item', 'release', 'item', 'jira', 'ask', 'report', 'note', 'decision', 'portfolio', 'event']);
|
|
26
26
|
const MIN_GAP_MS = 40000; // the companion runs at most this often per note
|
|
27
|
+
const AUTH_WAIT_MS = 120000; // after a run found Claude signed out, wait this long before the next try
|
|
27
28
|
const TAIL_MINUTES = 5; // how much of the talk the companion reads
|
|
28
29
|
const TAIL_MAX = 40; // and at most this many lines
|
|
29
30
|
const TOPICS_MAX = 8;
|
|
@@ -231,7 +232,7 @@ function foldState(lines, { topics = [], startMs = null, nowMs = Date.now() } =
|
|
|
231
232
|
return byKey.get(k);
|
|
232
233
|
};
|
|
233
234
|
for (const t of topics) addTopic(t.text || t, t.src || 'steps');
|
|
234
|
-
let current = null; const runs = []; let gist = ''; let chips = []; const notes = []; let seen = 0; let lastRunAt = 0; let pending = false; let error = null;
|
|
235
|
+
let current = null; const runs = []; let gist = ''; let chips = []; const notes = []; let seen = 0; let lastRunAt = 0; let pending = false; let error = null; let authAt = 0;
|
|
235
236
|
const asks = new Map();
|
|
236
237
|
const close = (at) => { const r = runs[runs.length - 1]; if (r && r.to == null) r.to = at; };
|
|
237
238
|
const switchTo = (row, at) => { if (!row || (current && current.id === row.id && runs.length && runs[runs.length - 1].to == null)) return; close(at); row.parked = false; current = row; runs.push({ topic: row.id, from: at, to: null }); };
|
|
@@ -240,9 +241,9 @@ function foldState(lines, { topics = [], startMs = null, nowMs = Date.now() } =
|
|
|
240
241
|
if (!l || typeof l !== 'object') continue;
|
|
241
242
|
const at = Number.isFinite(l.at) ? l.at : 0;
|
|
242
243
|
if (l.k === 'run') { lastRunAt = at; seen = Number.isFinite(l.lines) ? l.lines : seen; pending = true; }
|
|
243
|
-
else if (l.k === 'fail') { if (!l.id) { pending = false; error = str(l.error, 200) || 'The companion did not answer.'; } else if (asks.has(l.id)) asks.get(l.id).state = 'failed', asks.get(l.id).error = str(l.error, 200); }
|
|
244
|
+
else if (l.k === 'fail') { if (!l.id) { pending = false; error = str(l.error, 200) || 'The companion did not answer.'; authAt = l.auth ? at : 0; } else if (asks.has(l.id)) asks.get(l.id).state = 'failed', asks.get(l.id).error = str(l.error, 200); }
|
|
244
245
|
else if (l.k === 'out') {
|
|
245
|
-
pending = false; error = null;
|
|
246
|
+
pending = false; error = null; authAt = 0;
|
|
246
247
|
if (l.gist) gist = l.gist;
|
|
247
248
|
if (Array.isArray(l.chips)) chips = l.chips;
|
|
248
249
|
const tp = l.topic || {};
|
|
@@ -271,7 +272,7 @@ function foldState(lines, { topics = [], startMs = null, nowMs = Date.now() } =
|
|
|
271
272
|
const state = current && current.id === r.id ? 'now' : r.parked ? 'parked' : spent(r.id) > 0 ? 'discussed' : 'notyet';
|
|
272
273
|
return { id: r.id, text: r.text, src: r.src, state, minutes, ...(r.at ? { at: r.at } : {}), ...(r.by ? { by: r.by } : {}) };
|
|
273
274
|
});
|
|
274
|
-
return { gist, chips, current: current ? current.id : null, topics: outTopics, runs: outRuns, notes, seen, last_run_at: lastRunAt, pending, asks: [...asks.values()], error };
|
|
275
|
+
return { gist, chips, current: current ? current.id : null, topics: outTopics, runs: outRuns, notes, seen, last_run_at: lastRunAt, pending, asks: [...asks.values()], error, auth_at: authAt };
|
|
275
276
|
}
|
|
276
277
|
|
|
277
278
|
/* ── when the companion runs ────────────────────────────────────────────────────────────────────────────────────── */
|
|
@@ -283,6 +284,7 @@ const off = (env) => /^(off|0|false|no)$/i.test(String((env || process.env).PMOS
|
|
|
283
284
|
function shouldRun(now, state, note, opts = {}) {
|
|
284
285
|
const st = state || {};
|
|
285
286
|
if (off(opts.env)) return { run: false, reason: 'off' };
|
|
287
|
+
if (st.auth_at && now - st.auth_at < AUTH_WAIT_MS) return { run: false, reason: 'signed out' };
|
|
286
288
|
if (!note || note.state !== 'recording') return { run: false, reason: 'not recording' };
|
|
287
289
|
if (st.pending && st.last_run_at && now - st.last_run_at < (opts.timeoutMs || 20 * 60000)) return { run: false, reason: 'busy' };
|
|
288
290
|
if (!(note.lines > (st.seen || 0))) return { run: false, reason: 'no new lines' };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* lib/own-profile.js — PMOS's own Claude profile (initiative claude-own-login).
|
|
4
|
+
*
|
|
5
|
+
* By default a background run and a terminal session both use Claude Code's own folder, ~/.claude. A /login typed in a
|
|
6
|
+
* terminal then replaces the account PMOS's background runs use, and they fail with "OAuth session expired". PMOS holds no
|
|
7
|
+
* credential and cannot copy a login, so it can only keep the two apart: a profile of its own, `pmos`, whose
|
|
8
|
+
* CLAUDE_CONFIG_DIR is ~/.claude-pmos. The PM signs in to it once; terminal sessions (which take no profile) never touch it.
|
|
9
|
+
*
|
|
10
|
+
* ensure(homeDir, profilesDir) → { ok, id, dir, created } | { ok:false, error }
|
|
11
|
+
* makes ~/.claude-pmos (mode 0700) and ~/.claude/profiles/pmos.json when they are missing. It never overwrites a
|
|
12
|
+
* descriptor that exists, and it never sets the default: that is a separate press, after the PM has signed in.
|
|
13
|
+
*/
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const ID = 'pmos';
|
|
18
|
+
const dirOf = (home) => path.join(home, '.claude-pmos');
|
|
19
|
+
|
|
20
|
+
function ensure(home, profilesDir) {
|
|
21
|
+
const dir = dirOf(home);
|
|
22
|
+
const file = path.join(profilesDir, ID + '.json');
|
|
23
|
+
try {
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
25
|
+
fs.mkdirSync(profilesDir, { recursive: true, mode: 0o700 });
|
|
26
|
+
} catch (e) { return { ok: false, error: 'the folders could not be made — ' + String((e && e.code) || (e && e.message) || 'write failed').slice(0, 120) }; }
|
|
27
|
+
const desc = { id: ID, label: 'PMOS', env: { CLAUDE_CONFIG_DIR: dir } };
|
|
28
|
+
try {
|
|
29
|
+
fs.writeFileSync(file, JSON.stringify(desc, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
|
|
30
|
+
return { ok: true, id: ID, dir, created: true };
|
|
31
|
+
} catch (e) {
|
|
32
|
+
if (e && e.code === 'EEXIST') return { ok: true, id: ID, dir, created: false };
|
|
33
|
+
return { ok: false, error: 'the profile could not be written — ' + String((e && e.code) || (e && e.message) || 'write failed').slice(0, 120) };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { ID, dirOf, ensure };
|
|
@@ -129,6 +129,7 @@ function settleRun(ctx, key, done, now) {
|
|
|
129
129
|
const res = done && done.result;
|
|
130
130
|
if (done && done.stopped) return append(ctx.runtimeDir, key + '.jsonl', { k: 'fail', at, error: 'The run was stopped before it answered.' });
|
|
131
131
|
if (done && done.timedOut) return append(ctx.runtimeDir, key + '.jsonl', { k: 'fail', at, error: 'The companion took too long and was stopped.' });
|
|
132
|
+
if (res && headless.signedOut(res.text)) return append(ctx.runtimeDir, key + '.jsonl', { k: 'fail', at, auth: true, error: headless.SIGNED_OUT_WHY });
|
|
132
133
|
if (!res || !res.ok || res.isError) return append(ctx.runtimeDir, key + '.jsonl', { k: 'fail', at, error: 'The companion did not answer (' + ((done && done.note) || 'no result') + ').' });
|
|
133
134
|
const parsed = F.parseCompanion(res.text);
|
|
134
135
|
// a malformed answer is dropped, not shown
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.245.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.245.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -560,6 +560,7 @@ export function mountSettings(host, ctx = {}) {
|
|
|
560
560
|
extRow('sess.tier', seg('Model', [['auto', 'Auto'], ['fast', 'Fast'], ['balanced', 'Balanced'], ['deep', 'Deep']], d.tier, (v) => setDef({ tier: v }, 'sess.tier', { tier: d.tier })), { scope: 'computer' }),
|
|
561
561
|
extRow('sess.profile', profileSelect('c3set-sess-profile'), { scope: 'computer', forId: 'c3set-sess-profile', note: profs.length ? null : p ? 'No profile was found. Make one in the classic app\'s Settings, Profiles (/classic.html).' : 'Reading profiles.' }),
|
|
562
562
|
lockedRow('sess.flags', 'Launch flags', 'Flags for the claude command, such as a permission mode.', 'Set when you start a session', 'computer')], 'term'),
|
|
563
|
+
ownLoginSec(profs, def),
|
|
563
564
|
profs.length ? sect('Profiles', [list(profRows), help('A new profile is made in the classic app\'s Settings, Profiles (/classic.html).')], { ic: 'user', count: profs.length }) : null,
|
|
564
565
|
groupOf('sessions', 'Housekeeping', 'term'),
|
|
565
566
|
sect('Running now' + (M.running ? ' · ' + run.length + (max ? ' of ' + max : '') : ''), [M.running ? (run.length ? list(runRows) : fold(null, 'No sessions running.')) : fold(null, 'Reading the sessions.'),
|
|
@@ -567,6 +568,31 @@ export function mountSettings(host, ctx = {}) {
|
|
|
567
568
|
M.ws ? group('Workspace', [extRow('validate.consent', M.consent && !M.consent.error ? swBtn(M.consent.on, EXT['validate.consent'][2], async () => { const r = await pmPost('/api/validate/repo', { ws: M.ws, consent: !M.consent.on }); if (r && r.ok !== false) { M.consent.on = !!r.consent; toast('Saved: validate consent'); } else M.err['validate.consent'] = (r && r.error) || 'Not saved.'; draw(); }) : el('span', { class: 'dim' }, M.consent ? M.consent.error : 'Reading'), { scope: 'ws' })], 'term') : null];
|
|
568
569
|
}
|
|
569
570
|
|
|
571
|
+
/* PMOS's own Claude login (claude-own-login): a profile of its own, so a /login typed in a terminal never signs PMOS out. */
|
|
572
|
+
async function ownSignIn() {
|
|
573
|
+
delete M.err['sess.own'];
|
|
574
|
+
const made = await pmPost('/api/profiles/own', {});
|
|
575
|
+
if (!made || !made.ok) { M.err['sess.own'] = (made && made.error) || 'The profile was not made.'; draw(); return; }
|
|
576
|
+
const term = await import('./tab-term.js').catch(() => null);
|
|
577
|
+
const res = term ? await term.requestSpawn({ name: 'Sign in · PMOS Claude', agent: 'claude', profile: made.id, ws: M.ws, start: true, workType: 'general' }) : null;
|
|
578
|
+
pmGet('/api/profiles').then((r) => { M.profiles = r && r.ok !== false ? r : M.profiles; draw(); });
|
|
579
|
+
if (!res || !res.ok) { M.err['sess.own'] = (res && res.error) || 'The session did not start.'; draw(); return; }
|
|
580
|
+
const m = await import('./c3-session-term.js');
|
|
581
|
+
m.openSessionTerm({ id: res.id, name: 'Sign in · PMOS Claude' });
|
|
582
|
+
toast('Type /login in the terminal and sign in. Then press Use for PMOS.');
|
|
583
|
+
}
|
|
584
|
+
function ownLoginSec(profs, def) {
|
|
585
|
+
const own = profs.find((x) => x.id === 'pmos');
|
|
586
|
+
const using = !!own && def === 'pmos';
|
|
587
|
+
return sect('PMOS\'s own Claude login', [
|
|
588
|
+
help('A /login typed in a terminal replaces the account PMOS uses in the background. Give PMOS its own login and a terminal can never sign it out. Terminal sessions keep working as they do now: sign in or switch there whenever you like.'),
|
|
589
|
+
el('div', { class: 'iacts', 'data-c3set-own': '' },
|
|
590
|
+
using ? tag('In use', 'ok') : null,
|
|
591
|
+
btn(own ? 'Sign in again' : 'Set up', { variant: own && !using ? 'outline' : using ? 'ghost' : 'primary', attrs: { 'data-c3set-own-signin': '' }, disabled: !M.ws, onclick: ownSignIn }),
|
|
592
|
+
own && !using ? btn('Use for PMOS', { variant: 'primary', attrs: { 'data-c3set-own-use': '' }, onclick: () => chooseProfile('pmos') }) : null),
|
|
593
|
+
M.err['sess.own'] ? fold('alert', M.err['sess.own'], 'w') : null], { ic: 'user' });
|
|
594
|
+
}
|
|
595
|
+
|
|
570
596
|
function privacySec() {
|
|
571
597
|
const w = M.engine && M.engine.whisper; const j = M.engine && M.engine.jev; const o = M.online;
|
|
572
598
|
const jevOn = !!(j && (j.jev || j.check));
|
package/workspace/server.js
CHANGED
|
@@ -6707,6 +6707,15 @@ async function handleRequest(req, res) {
|
|
|
6707
6707
|
}
|
|
6708
6708
|
return json(res, { ok: true, profile: profileWire(made, read), path: file });
|
|
6709
6709
|
}
|
|
6710
|
+
/* PMOS's own Claude profile (lib/own-profile.js): makes ~/.claude-pmos and the descriptor `pmos`, once. It does not make it the
|
|
6711
|
+
* default and holds no login: the PM signs in to it in a session, then presses Make default. */
|
|
6712
|
+
if (p === '/api/profiles/own' && req.method === 'POST') {
|
|
6713
|
+
const made = require('./lib/own-profile.js').ensure(os.homedir(), profilesLib.profilesDir(os.homedir()));
|
|
6714
|
+
if (!made.ok) return json(res, { ok: false, error: made.error }, 500);
|
|
6715
|
+
const read = readProfilesNow();
|
|
6716
|
+
if (!(read.profiles || []).some((x) => x.id === made.id)) return json(res, { ok: false, error: 'the PMOS profile was written but could not be read back' }, 500);
|
|
6717
|
+
return json(res, { ok: true, id: made.id, created: made.created });
|
|
6718
|
+
}
|
|
6710
6719
|
if (p === '/api/profiles/prefer' && req.method === 'POST') {
|
|
6711
6720
|
let body = {};
|
|
6712
6721
|
try { body = JSON.parse((await readBody(req)) || '{}'); }
|