@golden-frijoles/cli 0.5.0 → 0.6.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/README.md CHANGED
@@ -95,6 +95,19 @@ already exists **moves** to this metric. The dry run says so before anything is
95
95
  reuse its key. The server validates the block and prints its `issues` on a 400. A file that still has the template's
96
96
  `<…>` placeholders is refused before anything is sent.
97
97
 
98
+ ## Reading a result: `gf north-star readings`, `gf experiments decision`
99
+
100
+ An agent reading an epic's result (the plugin's `epic-read`) fetches the number itself through these two reads. Any
101
+ project member can run them; `--json` prints the body the agent parses.
102
+
103
+ ```bash
104
+ gf north-star readings grounded_bets_share --to 2026-11-04 --json # the input's readings; `latest` is the number
105
+ gf experiments decision smart-defaults --json # the experiment's decision record
106
+ ```
107
+
108
+ `latest` is the last reading on or before `--to`; an input with no reading yet says so rather than reporting zero.
109
+ Cite them as `north-star:<input>@<latest.date>` and `ab:<experiment>`.
110
+
98
111
  ## Exit codes
99
112
 
100
113
  | Code | Name | Means |
@@ -14,6 +14,7 @@ const flags_write_1 = require("./flags-write");
14
14
  const flags_history_1 = require("./flags-history");
15
15
  const flags_sync_1 = require("./flags-sync");
16
16
  const north_star_1 = require("./north-star");
17
+ const result_reads_1 = require("./result-reads");
17
18
  const keys_1 = require("./keys");
18
19
  const doctor_1 = require("./doctor");
19
20
  const config_1 = require("./config");
@@ -41,6 +42,8 @@ exports.COMMANDS = [
41
42
  flags_history_1.flagsHistoryCommand,
42
43
  flags_sync_1.flagsSyncCommand,
43
44
  north_star_1.northStarSetCommand,
45
+ result_reads_1.northStarReadingsCommand,
46
+ result_reads_1.experimentsDecisionCommand,
44
47
  keys_1.keysLsCommand,
45
48
  keys_1.keysCreateCommand,
46
49
  keys_1.keysRevokeCommand,
@@ -0,0 +1,36 @@
1
+ import type { Command } from '../command';
2
+ export type InputReadingsBody = {
3
+ project: string;
4
+ metric: string | null;
5
+ input: {
6
+ key: string;
7
+ name: string;
8
+ valueSource: string;
9
+ };
10
+ readings: Array<{
11
+ date: string;
12
+ value: number;
13
+ }>;
14
+ latest: {
15
+ date: string;
16
+ value: number;
17
+ } | null;
18
+ };
19
+ export type ExperimentDecisionBody = {
20
+ project: string;
21
+ key: string;
22
+ version: number;
23
+ lifecycle: string;
24
+ decisions: {
25
+ state: 'undecided' | 'decided';
26
+ current: {
27
+ outcome?: string;
28
+ chosenVariantKey?: string | null;
29
+ rationale?: string;
30
+ createdAt?: string;
31
+ } | null;
32
+ history: unknown[];
33
+ };
34
+ };
35
+ export declare const northStarReadingsCommand: Command;
36
+ export declare const experimentsDecisionCommand: Command;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ // result-record · Story 3.2 (D16) — the two reads an agent fetches an epic's result through.
3
+ //
4
+ // gf north-star readings <input> [--to <day>] an input's readings, and the latest on or before --to
5
+ // gf experiments decision <key> [--version <n>] an experiment's decision record (latest version by default)
6
+ //
7
+ // Both are `--json`-first: `epic-read` spawns them and reads the body, so the JSON is the contract and the table is for
8
+ // a person. Both go through `/api/v1/cli/*`, which checks membership exactly as the console does (404 elsewhere).
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.experimentsDecisionCommand = exports.northStarReadingsCommand = void 0;
11
+ const args_1 = require("../args");
12
+ const exit_codes_1 = require("../exit-codes");
13
+ const output_1 = require("../output");
14
+ const flags_read_1 = require("./flags-read");
15
+ const DAY = /^\d{4}-\d{2}-\d{2}$/;
16
+ exports.northStarReadingsCommand = {
17
+ path: ['north-star', 'readings'],
18
+ summary: "one North Star input's readings, and the latest on or before a day",
19
+ usage: 'gf north-star readings <input> [--to <YYYY-MM-DD>] [--project <slug>] [--json]',
20
+ needsAuth: true,
21
+ detail: `An agent reading an epic's result calls this: \`latest\` is the number it reports, and
22
+ \`north-star:<input>@<latest.date>\` is the evidence it writes. Nothing is invented: an input
23
+ with no reading on or before --to says so, rather than reporting zero.`,
24
+ flags: [
25
+ { name: 'to', value: '<YYYY-MM-DD>', describe: 'cut the readings at this day (default: all of them)' },
26
+ { name: 'project', value: '<slug>', describe: 'the project (default: the remembered one)' },
27
+ ],
28
+ async run(context) {
29
+ const input = context.args.positionals[0];
30
+ if (!input) {
31
+ context.emit.fail('invalid', 'Name the input: `gf north-star readings <input-key>`.');
32
+ return exit_codes_1.EXIT.USAGE;
33
+ }
34
+ const to = (0, args_1.flagValue)(context.args, 'to');
35
+ if (to !== undefined && !DAY.test(to)) {
36
+ context.emit.fail('invalid', '--to must be a day written YYYY-MM-DD.');
37
+ return exit_codes_1.EXIT.USAGE;
38
+ }
39
+ const project = (0, flags_read_1.resolveProject)(context);
40
+ if (!project)
41
+ return (0, flags_read_1.missingProject)(context);
42
+ const result = await context.api.get('api/v1/cli/north-star/readings', {
43
+ project,
44
+ input,
45
+ ...(to ? { to } : {}),
46
+ });
47
+ if (result.kind === 'network') {
48
+ context.emit.fail('server_error', result.message);
49
+ return exit_codes_1.EXIT.SERVER;
50
+ }
51
+ if (result.kind === 'error') {
52
+ context.emit.fail(result.code, result.message);
53
+ return (0, exit_codes_1.exitForServerCode)(result.code);
54
+ }
55
+ const body = result.body;
56
+ const human = body.readings.length === 0
57
+ ? `${body.input.key}: no readings${to ? ` on or before ${to}` : ''} yet.`
58
+ : [
59
+ `${body.input.key} (${body.input.name}) — latest ${body.latest.value} on ${body.latest.date}`,
60
+ (0, output_1.table)(['DAY', 'VALUE'], body.readings.slice(-10).map((r) => [r.date, String(r.value)])),
61
+ ].join('\n');
62
+ context.emit.ok({
63
+ project: body.project,
64
+ metric: body.metric,
65
+ input: body.input,
66
+ readings: body.readings,
67
+ latest: body.latest,
68
+ }, human);
69
+ return exit_codes_1.EXIT.OK;
70
+ },
71
+ };
72
+ exports.experimentsDecisionCommand = {
73
+ path: ['experiments', 'decision'],
74
+ summary: "an experiment's decision record (the latest version unless --version)",
75
+ usage: 'gf experiments decision <key> [--version <n>] [--project <slug>] [--json]',
76
+ needsAuth: true,
77
+ detail: `What was decided about an A/B test, from the experiment's append-only decision ledger.
78
+ An agent reading an epic's result cites it as \`ab:<key>\`.`,
79
+ flags: [
80
+ { name: 'version', value: '<n>', describe: 'the definition version (default: the latest)' },
81
+ { name: 'project', value: '<slug>', describe: 'the project (default: the remembered one)' },
82
+ ],
83
+ async run(context) {
84
+ const key = context.args.positionals[0];
85
+ if (!key) {
86
+ context.emit.fail('invalid', 'Name the experiment: `gf experiments decision <key>`.');
87
+ return exit_codes_1.EXIT.USAGE;
88
+ }
89
+ const version = (0, args_1.flagValue)(context.args, 'version');
90
+ if (version !== undefined && !/^[1-9]\d{0,6}$/.test(version)) {
91
+ context.emit.fail('invalid', '--version must be a whole number from 1.');
92
+ return exit_codes_1.EXIT.USAGE;
93
+ }
94
+ const project = (0, flags_read_1.resolveProject)(context);
95
+ if (!project)
96
+ return (0, flags_read_1.missingProject)(context);
97
+ const result = await context.api.get('api/v1/cli/experiments/decision', {
98
+ project,
99
+ experiment: key,
100
+ ...(version ? { version } : {}),
101
+ });
102
+ if (result.kind === 'network') {
103
+ context.emit.fail('server_error', result.message);
104
+ return exit_codes_1.EXIT.SERVER;
105
+ }
106
+ if (result.kind === 'error') {
107
+ context.emit.fail(result.code, result.message);
108
+ return (0, exit_codes_1.exitForServerCode)(result.code);
109
+ }
110
+ const body = result.body;
111
+ const current = body.decisions.current;
112
+ context.emit.ok({
113
+ project: body.project,
114
+ key: body.key,
115
+ version: body.version,
116
+ lifecycle: body.lifecycle,
117
+ decisions: body.decisions,
118
+ }, current
119
+ ? `${body.key} v${body.version} (${body.lifecycle}) — decided: ${current.outcome ?? 'recorded'}${current.chosenVariantKey ? ` (${current.chosenVariantKey})` : ''}${current.rationale ? `\n ${current.rationale}` : ''}`
120
+ : `${body.key} v${body.version} (${body.lifecycle}) — no decision recorded yet.`);
121
+ return exit_codes_1.EXIT.OK;
122
+ },
123
+ };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.5.0";
1
+ export declare const VERSION = "0.6.0";
package/dist/version.js CHANGED
@@ -10,4 +10,4 @@ exports.VERSION = void 0;
10
10
  //
11
11
  // It is a literal, and `version.test.ts` asserts it equals `package.json`'s — so the two cannot
12
12
  // drift, and the drift is caught by the unit gate rather than by someone reading `gf --version`.
13
- exports.VERSION = '0.5.0';
13
+ exports.VERSION = '0.6.0';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@golden-frijoles/cli",
3
3
  "private": false,
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "description": "The Golden Frijoles CLI — create, roll out and kill feature flags from a terminal, or from an agent.",
6
6
  "bin": {
7
7
  "gf": "./dist/bin.js"