@bli-cockpit/cli 0.2.279 → 0.2.280

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.
@@ -0,0 +1,40 @@
1
+ import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor } from "./agent-door.js";
2
+ import { writeLine } from "./cli-io.js";
3
+ const TAG = "[ai-skills cli]";
4
+ const READ_DEADLINE_MS = 55_000;
5
+ /** The door's own query: only what the person asked for, so the door's defaults stay the door's. */
6
+ export function aiSkillsQuery(command) {
7
+ const query = new URLSearchParams();
8
+ if (command.who)
9
+ query.set("who", command.who);
10
+ if (command.weeks !== undefined)
11
+ query.set("weeks", String(command.weeks));
12
+ if (command.week)
13
+ query.set("week", command.week);
14
+ return query.toString();
15
+ }
16
+ export async function runAiSkills(command, io) {
17
+ let door;
18
+ try {
19
+ door = await openAgentDoor("ai-skills", command, io);
20
+ }
21
+ catch (error) {
22
+ const detail = error instanceof Error ? error.message : "this machine is not paired with Tower";
23
+ writeLine(io.stderr, `${TAG} refused ${JSON.stringify({ reason: "not_paired" })}`);
24
+ if (command.json)
25
+ writeLine(io.stdout, JSON.stringify({ ok: false, error: "not_paired", detail }));
26
+ else
27
+ writeLine(io.stderr, detail);
28
+ return 1;
29
+ }
30
+ const asked = aiSkillsQuery(command);
31
+ const answer = await askAgentDoor(door, { path: asked ? `/api/ai-skills?${asked}` : "/api/ai-skills", method: "GET", label: "ai-skills read", timeoutMs: READ_DEADLINE_MS });
32
+ if (!answer.ok)
33
+ return failAgentDoor(door, TAG, answer.reason, answer.detail);
34
+ const body = answer.body;
35
+ writeLine(io.stderr, `${TAG} read answered ${JSON.stringify({ scope: body.scope, people: body.people.length, weeks: body.people[0]?.weeks.length ?? 0, sessions: body.sessions?.length ?? null })}`);
36
+ if (command.json)
37
+ return emitAgentDoor(door, body);
38
+ writeLine(io.stdout, body.text);
39
+ return 0;
40
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `cockpit ai-skills` argument parsing (BLI-23364).
3
+ *
4
+ * cockpit ai-skills [--who <me|team|email|user-id>] [--weeks <n>] [--week <YYYY-MM-DD>] [--json]
5
+ *
6
+ * The six AI-skill scores per week over `GET /api/ai-skills` with this machine's device token. Who may see what is the
7
+ * database's rule and the door's refusal, not this parser's.
8
+ */
9
+ import { optionalNonEmpty, optionalPositiveInteger, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
10
+ export function parseAiSkillsArgs(args) {
11
+ const values = parseNamedArgs(args, {
12
+ allowedFlags: ["--who", "--weeks", "--week", "--home", "--dashboard-url", "--json"],
13
+ valueFlags: ["--who", "--weeks", "--week", "--home", "--dashboard-url"],
14
+ });
15
+ if (values.positionals.length > 0) {
16
+ throw new Error("ai-skills takes no verb. Example: `cockpit ai-skills`, `cockpit ai-skills --weeks 12` or `cockpit ai-skills --who team`.");
17
+ }
18
+ const weeks = optionalPositiveInteger(values.flags.get("--weeks"), "--weeks");
19
+ if (weeks !== undefined && weeks > 26)
20
+ throw new Error("--weeks must be 26 or fewer.");
21
+ const week = optionalNonEmpty(values.flags.get("--week"));
22
+ if (week !== undefined && !/^\d{4}-\d{2}-\d{2}$/.test(week))
23
+ throw new Error("--week wants a Monday as YYYY-MM-DD (weeks run Monday to Sunday in UTC).");
24
+ const who = optionalNonEmpty(values.flags.get("--who"))?.trim();
25
+ if (week !== undefined && who === "team")
26
+ throw new Error("--week reads one person's sessions: use --who me or --who <email>.");
27
+ return {
28
+ kind: "ai-skills",
29
+ dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")),
30
+ homeDir: optionalNonEmpty(values.flags.get("--home")),
31
+ json: values.booleans.has("--json"),
32
+ ...(who ? { who } : {}),
33
+ ...(weeks !== undefined ? { weeks } : {}),
34
+ ...(week !== undefined ? { week } : {}),
35
+ };
36
+ }
@@ -44,6 +44,7 @@ export { parseUsageArgs } from "./local-args-tower-usage.js";
44
44
  export { parseLogsArgs } from "./local-args-tower-logs.js";
45
45
  export { parseBrainArgs } from "./local-args-tower-brain.js";
46
46
  export { parseHitsArgs } from "./local-args-tower-hits.js";
47
+ export { parseAiSkillsArgs } from "./local-args-tower-ai-skills.js";
47
48
  export { parseIntentionsArgs } from "./local-args-tower-intentions.js";
48
49
  export { parseWhoArgs } from "./local-args-tower-who.js";
49
50
  export { parsePeopleArgs } from "./local-args-tower-people.js";
@@ -54,6 +55,7 @@ export { parseImageArgs } from "./local-args-tower-image.js";
54
55
  export { parseCareersArgs } from './local-args-tower-careers.js';
55
56
  export { parseRenameArgs } from './local-args-tower-rename.js';
56
57
  export { parseGlossaryArgs } from "./glossary.js";
58
+ export { parseSkillsArgs } from "./skills.js";
57
59
  export { parseSpendArgs } from "./spend.js";
58
60
  export { parsePushArgs } from "./push.js";
59
61
  export { parseCiArgs } from "./ci.js";
@@ -16,6 +16,7 @@
16
16
  * verbatim, no logic change.
17
17
  */
18
18
  import { parseGlossaryArgs } from "./glossary.js";
19
+ import { parseSkillsArgs } from "./skills.js";
19
20
  import { parseSpendArgs } from "./spend.js";
20
21
  import { parsePushArgs } from "./push.js";
21
22
  import { parseRootsArgs } from "./roots.js";
@@ -23,7 +24,7 @@ import { parseSlackPrivacyArgs } from "./slack-privacy-args.js";
23
24
  import { parseMilestoneArgs } from "./local-args-tower-milestone.js";
24
25
  import { SURFACE_ALIASES } from "./local-help.js";
25
26
  import { parseAgentRulesArgs, parseAnalyzeArgs, parseAutostartArgs, parseBackfillArgs, parseCleanArgs, parseDoctorArgs, parseInstallArgs, parseLoginArgs, parseMemoryArgs, parseLogoutArgs, parseOnboardArgs, parseReleaseArgs, parseServeArgs, parseSessionsArgs, parseSourcesArgs, parseStartArgs, parseStatusArgs, parseSyncArgs, parseUpdateArgs, } from "./local-args-collector.js";
26
- import { parseCiArgs, parseDeliveryArgs, parsePrOutputArgs, parseAiSpendArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
27
+ import { parseCiArgs, parseDeliveryArgs, parsePrOutputArgs, parseAiSpendArgs, parseBriefArgs, parseCorrectArgs, parseDocsArgs, parseIssueArgs, parseJarvisArgs, parseMailArgs, parseCalArgs, parseModelArgs, parseMsgArgs, parseNotesArgs, parseOpsArgs, parseProjectArgs, parseModelsArgs, parseScoutArgs, parseGithubArgs, parseSearchArgs, parseSettingsArgs, parseSlackArgs, parseTeamArgs, parseWorkbookArgs, parseUsageArgs, parseSetupArgs, parseSpeedArgs, parseLogsArgs, parseBrainArgs, parseHitsArgs, parseAiSkillsArgs, parseIntentionsArgs, parseWhoArgs, parsePeopleArgs, parseApprovalsArgs, parseDrillsArgs, parseManagerArgs, parseImageArgs, parseCareersArgs, parseRenameArgs, } from "./local-args-tower.js";
27
28
  // `normalizeUrl` has always been part of this module's surface — `local.ts` and
28
29
  // `local-auth.ts` import it from here — so it stays exported from this address
29
30
  // even though it now lives next door. The same goes for the four names the
@@ -88,6 +89,7 @@ const COMMAND_PARSERS = new Map([
88
89
  ["clean", (_command, rest) => parseCleanArgs(rest)],
89
90
  ["sources", (_command, rest) => parseSourcesArgs(rest)],
90
91
  ["glossary", (_command, rest) => parseGlossaryArgs(rest)],
92
+ ["skills", (_command, rest) => parseSkillsArgs(rest)], // BLI-23364 D: the Skill Library
91
93
  ["spend", (_command, rest) => parseSpendArgs(rest)],
92
94
  ["push", (_command, rest) => parsePushArgs(rest)],
93
95
  ["docs", (_command, rest) => parseDocsArgs(rest)],
@@ -108,6 +110,7 @@ const COMMAND_PARSERS = new Map([
108
110
  ["logs", (_command, rest) => parseLogsArgs(rest)],
109
111
  ["brain", (_command, rest) => parseBrainArgs(rest)],
110
112
  ["hits", (_command, rest) => parseHitsArgs(rest)],
113
+ ["ai-skills", (_command, rest) => parseAiSkillsArgs(rest)],
111
114
  ["intentions", (_command, rest) => parseIntentionsArgs(rest)],
112
115
  ["who", (_command, rest) => parseWhoArgs(rest)],
113
116
  ["people", (_command, rest) => parsePeopleArgs(rest)],
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `cockpit ai-skills` command shape (BLI-23364), beside `local-command-shapes-hits.ts` for the same reason:
3
+ * `local-command-shapes.ts` sits at the readability ceiling.
4
+ */
5
+ export {};
@@ -23,6 +23,7 @@ import { TOWER_OPS_COMMAND_HELP } from "./local-help-commands-tower-ops.js";
23
23
  /** One manual per collector command; the Tower nouns are spread in from their own file. */
24
24
  const COMMAND_HELP = new Map([
25
25
  ["glossary", ["Usage: cockpit glossary list|add <wrong> --to <name>|remove <wrong> [--json]", "Quote phrases. Removing a form prevents future corrections of it."]],
26
+ ["skills", ["Usage: cockpit skills library [--weeks <1-52>] [--json] | cockpit skills refresh [--weeks <1-104>] [--json]", "The Skill Library: every agent skill and command our GitHub repos define (name, description, repo, path, versions) with weekly use by person, from the sessions Tower already collects. Uses that match no scanned skill are listed as unmatched, never dropped. The first line is the coverage: repos scanned, skills, invocations matched of total. A member sees their own usage; a super_admin sees everyone's except an owner's. refresh scans the repos and recomputes the weekly totals now (super_admin only; a stale library also refreshes itself when read). MCP twins: skills_library, skills_refresh. Web: TEAM > Skills."]],
26
27
  ["push", ["Usage: cockpit push status | test [--dashboard-url <url>] [--json]", "Phone notifications. status lists your saved devices and whether the server has its push keys; test sends a test push to every saved device. Turn a phone on from Settings on the phone."]],
27
28
  ["spend", ["Usage: cockpit spend memory [--days 1|7|30] [--dashboard-url <url>] [--json]", "Spend by memory operation (write, recall, dream, update_forget, backfill, label, exam, predictor, people, who, intentions, salience, chat, other), ranked top first with list and real dollars, share, trend against the previous window, and how much of the real OpenAI bill the ledger explains. What it does not explain is shown as untagged. A super_admin reads it."]],
28
29
  ["roots", ["Usage: cockpit roots add <dir> [--allow-home-root] [--json]", "Approves a work directory, saves an approval receipt, and adds it to collection. Run cockpit doctor afterward."]],
@@ -275,6 +276,25 @@ const COMMAND_HELP = new Map([
275
276
  // BLI-10445: JARVIS, models, Scout and ops live in `local-help-commands-tower-ops.ts`;
276
277
  // slack, workbook, brief, notes and memory joined `local-help-commands-tower.ts`.
277
278
  ...TOWER_OPS_COMMAND_HELP,
279
+ [
280
+ "ai-skills",
281
+ [
282
+ "Usage: cockpit ai-skills [--who <me|team|email|user-id>] [--weeks <n>] [--week <YYYY-MM-DD>] [--dashboard-url <url>] [--json]",
283
+ "",
284
+ "The six AI-skill scores. Planning, Verification, Context, Parallelization, Collaboration and Ownership, each 0 to 10, judged",
285
+ "per session from your own turns and what the session achieved (checks that ran and passed, failures fixed, pull requests), then averaged",
286
+ "per week over the sessions that had evidence for that dimension. Every week prints its coverage (\"3 of 7 sessions scored this week\") and",
287
+ "each score its bucket: below 4 needs work, 4 to under 7 acceptable, 7 and above expert. A dimension with no evidence prints \"not enough",
288
+ "evidence\" and never a number. Weeks run Monday to Sunday in UTC, by the day each session started.",
289
+ "--weeks <n> how many weeks, newest first (default 8, at most 26).",
290
+ "--week <date> a Monday: add that week's judged sessions with the verbatim quotes of your own turns behind each score (one person).",
291
+ "--who <w> me (default), team, or one person's exact email or user id. A member reads only their own; team and other people are admin only,",
292
+ " and an owner's scores are visible to that owner alone (asking for them answers an empty list).",
293
+ "--json writes one object to stdout: the door's whole answer (people, weeks, dimensions with score, sessions, bucket, note, thresholds) and a text field;",
294
+ "a refusal is { ok: false, error, detail } and exits 1 (admin_only, invalid_weeks, unknown_person, not_paired, ...).",
295
+ "Teaching: docs/architecture/ai-skill-scores.md. The bli-tower MCP server has the twin: ai_skills_read.",
296
+ ],
297
+ ],
278
298
  [
279
299
  "correct",
280
300
  [
@@ -75,6 +75,7 @@ export const rootCommandNames = new Set([
75
75
  "clean",
76
76
  "sources",
77
77
  "glossary",
78
+ "skills",
78
79
  "spend",
79
80
  "push",
80
81
  "docs",
@@ -95,6 +96,7 @@ export const rootCommandNames = new Set([
95
96
  "logs",
96
97
  "brain",
97
98
  "hits",
99
+ "ai-skills",
98
100
  "intentions",
99
101
  "who",
100
102
  "people",
@@ -162,6 +164,7 @@ const COMMAND_SUMMARY_LINES = [
162
164
  " cockpit release [--dry-run] [--skip-checks] [--no-floor] [--tag <tag>] [--access <public|restricted>] [--otp <code>]",
163
165
  " cockpit careers [list|show <id>|rescreen <id>|send-takehome <id>|decide <id> --decision <passed_on|archived|reopen>|followup <id> [--record-sent-at <iso>]|interview <id> <--at <iso>|--clear>|grade <id> [--now]|resume <id> [--out <path>]|takehome show <role>|takehome set <role> --body-file <path>|settings show|settings set --enabled <true|false> --followup-enabled <true|false> --followup-after-days <n> --takehome-window-hours <n>] [--role <slug>] [--min-score <n>] [--since <date>] [--stage <s>] [--force] [--json]",
164
166
  " cockpit glossary list|add <wrong> --to <name>|remove <wrong> [--dashboard-url <url>] [--json]",
167
+ " cockpit skills library [--weeks <1-52>] | refresh [--weeks <1-104>] [--dashboard-url <url>] [--json]",
165
168
  " cockpit spend memory [--days 1|7|30] [--dashboard-url <url>] [--json]",
166
169
  " cockpit push status|test [--dashboard-url <url>] [--json]",
167
170
  " cockpit rename [list|resolve <id> --scope <page|everywhere|undo>] [--dashboard-url <url>] [--json]",
@@ -175,6 +178,7 @@ const COMMAND_SUMMARY_LINES = [
175
178
  " cockpit hits timeline [--weeks <n>] [--dashboard-url <url>] [--json]",
176
179
  " cockpit hits coach [--dashboard-url <url>] [--json]",
177
180
  " cockpit hits approve --scope <team|agents|project:<id>> [--version <n>] [--dashboard-url <url>] [--json]",
181
+ " cockpit ai-skills [--who <me|team|email|user-id>] [--weeks <n>] [--week <YYYY-MM-DD>] [--dashboard-url <url>] [--json]",
178
182
  " cockpit approvals [list|show <id>|create|approve <id> [--edit]|edit <id> [--approve]|reject <id>|rules [--active true|false]|rules create <card-id> [--any-audience] [--label <text>]|rules revoke <rule-id> [--reason <text>]] [--status <s>] [--limit <n>] [--before <cursor>] [--dashboard-url <url>] [--json]",
179
183
  " cockpit drills [list|start|answer --confidence <1-5> [--text <words>] [--attempt <id>]|theme|theme set <theme> [--play <t>] [--watch <t>] [--train <t>]|review] [--dashboard-url <url>] [--json]",
180
184
  " cockpit manager <clients|client <client>|run <run-id>|list <client>|queue <client>|show <id>|add <client> --title <t>|edit <id> --version <v>|done|reopen|link|approve|dismiss|proposal|archive|unarchive> [--timeline] [--dashboard-url <url>] [--json]",
@@ -136,6 +136,7 @@ async function runTowerCommand(command, io) {
136
136
  case "correct": return await (await import("./correct.js")).runCorrect(command, io);
137
137
  case "notes": return await (await import("./notes.js")).runNotes(command, io);
138
138
  case "glossary": return await (await import("./glossary.js")).runGlossary(command, io);
139
+ case "skills": return await (await import("./skills.js")).runSkills(command, io);
139
140
  case "sessions-forget": return await (await import("./sessions-forget.js")).runSessionsForget(command, io);
140
141
  case "spend": return await (await import("./spend.js")).runSpend(command, io);
141
142
  case "push": return await (await import("./push.js")).runPush(command, io);
@@ -155,6 +156,7 @@ async function runTowerCommand(command, io) {
155
156
  case "logs": return await (await import("./logs.js")).runLogs(command, io);
156
157
  case "brain": return await (await import("./brain.js")).runBrain(command, io);
157
158
  case "hits": return await (await import("./hits.js")).runHits(command, io);
159
+ case "ai-skills": return await (await import("./ai-skills.js")).runAiSkills(command, io);
158
160
  case "intentions": return await (await import("./intentions.js")).runIntentions(command, io);
159
161
  case "who": return await (await import("./who.js")).runWho(command, io);
160
162
  case "people": return await (await import("./people.js")).runPeople(command, io);
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
21
21
  }
22
22
 
23
23
  if (command === "--version" || command === "-V" || command === "version") {
24
- writeLine(io?.stdout ?? process.stdout, "0.2.279");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.280");
25
25
  return 0;
26
26
  }
27
27
 
@@ -0,0 +1,38 @@
1
+ import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor } from "./agent-door.js";
2
+ import { writeLine } from "./cli-io.js";
3
+ import { optionalNonEmpty, optionalUrl, parseNamedArgs } from "./local-arg-values.js";
4
+ export const SKILLS_USAGE = "Usage: cockpit skills library [--weeks <1-52>] [--json] | cockpit skills refresh [--weeks <1-104>] [--json]";
5
+ export function parseSkillsArgs(args) {
6
+ const values = parseNamedArgs(args, { allowedFlags: ["--weeks", "--json", "--home", "--dashboard-url"], valueFlags: ["--weeks", "--home", "--dashboard-url"] });
7
+ const [action, ...rest] = values.positionals;
8
+ if ((action !== "library" && action !== "refresh") || rest.length > 0)
9
+ throw new Error(SKILLS_USAGE);
10
+ const raw = values.flags.get("--weeks");
11
+ const max = action === "library" ? 52 : 104;
12
+ const weeks = raw === undefined ? undefined : Number(raw);
13
+ if (weeks !== undefined && (!Number.isInteger(weeks) || weeks < 1 || weeks > max))
14
+ throw new Error(SKILLS_USAGE);
15
+ return { kind: "skills", action, weeks, json: values.booleans.has("--json"), homeDir: optionalNonEmpty(values.flags.get("--home")), dashboardUrl: optionalUrl(values.flags.get("--dashboard-url")) };
16
+ }
17
+ export async function runSkills(command, io) {
18
+ const door = await openAgentDoor("skills", command, io);
19
+ if (command.action === "refresh") {
20
+ const answer = await askAgentDoor(door, { path: "/api/skills/library/refresh", method: "POST", label: "skills refresh", timeoutMs: 300_000, body: command.weeks === undefined ? {} : { weeks: command.weeks } });
21
+ if (!answer.ok)
22
+ return failAgentDoor(door, "[skills cli]", answer.reason, answer.detail);
23
+ if (command.json)
24
+ return emitAgentDoor(door, answer.body);
25
+ const body = answer.body;
26
+ const t = body.totals ?? {};
27
+ writeLine(io.stdout, `Skill Library refresh ${body.status ?? "finished"}${body.reason ? ` (${body.reason})` : ""}: ${t["repos_scanned"] ?? 0} repos scanned, ${t["files_seen"] ?? 0} skill files, ${t["versions_added"] ?? 0} new versions, ${t["weekly_rows"] ?? 0} weekly usage rows over ${t["weeks"] ?? "?"} weeks.`);
28
+ return body.ok === false ? 1 : 0;
29
+ }
30
+ const answer = await askAgentDoor(door, { path: `/api/skills/library${command.weeks === undefined ? "" : `?weeks=${command.weeks}`}`, method: "GET", label: "skills library", timeoutMs: 30_000 });
31
+ if (!answer.ok)
32
+ return failAgentDoor(door, "[skills cli]", answer.reason, answer.detail);
33
+ if (command.json)
34
+ return emitAgentDoor(door, answer.body);
35
+ for (const line of answer.body.lines)
36
+ writeLine(io.stdout, line);
37
+ return 0;
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.279",
3
+ "version": "0.2.280",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,9 +27,9 @@
27
27
  "test": "node dist/cli.js --help && node ../../scripts/assert-public-cli-routing.mjs && node ../../scripts/assert-public-cli-verb-help.mjs && node ../../scripts/assert-public-cli-surface-doors.mjs && node ../../scripts/assert-public-cli-runtime-files.mjs && node ../../scripts/assert-public-cli-exit-contract.mjs && node ../../scripts/assert-public-cli-no-fleet-posts.mjs && node ../../scripts/assert-public-cli-doctor-json.mjs && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
28
28
  },
29
29
  "dependencies": {
30
- "@bli-cockpit/memory-mcp": "0.1.86",
31
- "@bli-cockpit/mcp": "0.1.170",
32
- "@bli-cockpit/telemetry-core": "0.1.93",
30
+ "@bli-cockpit/memory-mcp": "0.1.87",
31
+ "@bli-cockpit/mcp": "0.1.171",
32
+ "@bli-cockpit/telemetry-core": "0.1.94",
33
33
  "unpdf": "1.8.1"
34
34
  },
35
35
  "repository": {