@golden-frijoles/cli 0.1.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.
Files changed (44) hide show
  1. package/README.md +125 -0
  2. package/dist/api.d.ts +28 -0
  3. package/dist/api.js +104 -0
  4. package/dist/args.d.ts +26 -0
  5. package/dist/args.js +156 -0
  6. package/dist/bin.d.ts +2 -0
  7. package/dist/bin.js +18 -0
  8. package/dist/command.d.ts +75 -0
  9. package/dist/command.js +34 -0
  10. package/dist/commands/auth.d.ts +4 -0
  11. package/dist/commands/auth.js +214 -0
  12. package/dist/commands/doctor.d.ts +9 -0
  13. package/dist/commands/doctor.js +239 -0
  14. package/dist/commands/flags-history.d.ts +3 -0
  15. package/dist/commands/flags-history.js +183 -0
  16. package/dist/commands/flags-read.d.ts +71 -0
  17. package/dist/commands/flags-read.js +124 -0
  18. package/dist/commands/flags-sync.d.ts +2 -0
  19. package/dist/commands/flags-sync.js +129 -0
  20. package/dist/commands/flags-write.d.ts +6 -0
  21. package/dist/commands/flags-write.js +311 -0
  22. package/dist/commands/index.d.ts +2 -0
  23. package/dist/commands/index.js +40 -0
  24. package/dist/commands/init.d.ts +33 -0
  25. package/dist/commands/init.js +458 -0
  26. package/dist/commands/keys.d.ts +4 -0
  27. package/dist/commands/keys.js +177 -0
  28. package/dist/commands/projects.d.ts +4 -0
  29. package/dist/commands/projects.js +114 -0
  30. package/dist/credentials.d.ts +60 -0
  31. package/dist/credentials.js +120 -0
  32. package/dist/exit-codes.d.ts +24 -0
  33. package/dist/exit-codes.js +70 -0
  34. package/dist/help.d.ts +49 -0
  35. package/dist/help.js +118 -0
  36. package/dist/index.d.ts +7 -0
  37. package/dist/index.js +28 -0
  38. package/dist/output.d.ts +26 -0
  39. package/dist/output.js +68 -0
  40. package/dist/run.d.ts +13 -0
  41. package/dist/run.js +135 -0
  42. package/dist/version.d.ts +1 -0
  43. package/dist/version.js +13 -0
  44. package/package.json +40 -0
package/dist/help.js ADDED
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ // golden-frijoles-cli · Sprint 1, Story 1.1 — `--help`, rendered from the command table.
3
+ //
4
+ // ── D5: this output is a CONTRACT ─────────────────────────────────────────────────────────────
5
+ // `src/__golden__/help.txt` holds these exact bytes and `golden.test.ts` diffs them. That is not
6
+ // pedantry about copy: an agent handed `gf --help` reads it to learn the verbs and the flags, so a
7
+ // silent change to this text changes what every agent believes the tool can do. A golden file makes
8
+ // the change arrive in review, where someone can decide it is intended.
9
+ //
10
+ // Nothing here is hand-maintained prose about which verbs exist. It is rendered from `COMMANDS`, so
11
+ // the help and the dispatcher cannot disagree.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.helpAsData = helpAsData;
14
+ exports.commandAsData = commandAsData;
15
+ exports.renderRootHelp = renderRootHelp;
16
+ exports.renderCommandHelp = renderCommandHelp;
17
+ const exit_codes_1 = require("./exit-codes");
18
+ const output_1 = require("./output");
19
+ const HEADER = `gf — the Golden Frijoles CLI
20
+
21
+ Create a flag in every environment, roll it out, and kill it, from a terminal
22
+ or from an agent. Every command takes --json.`;
23
+ const GLOBAL_FLAGS = `Global flags
24
+ --json machine-readable output on stdout, and nothing else
25
+ --project <slug> act on this project (default: the one from \`gf projects use\`)
26
+ --api <url> the deployment to talk to (default: the one you logged in to)
27
+ --token <token> use this credential instead of the saved one
28
+ --help, -h this text, or a verb's own
29
+ --version, -V print the version and exit`;
30
+ const ENVIRONMENT = `Environment
31
+ GOLDEN_FRIJOLES_TOKEN a CLI token. Wins over the saved credential — this is the CI path,
32
+ and it means CI never has to write a token to disk.
33
+ GOLDEN_FRIJOLES_URL the deployment to talk to.
34
+ GOLDEN_FRIJOLES_PROJECT the active project.`;
35
+ /** The verb list, aligned. Grouped by first word so `flags` reads as a family. */
36
+ function verbList(commands) {
37
+ const width = Math.max(...commands.map((command) => command.path.join(' ').length));
38
+ const groups = new Map();
39
+ for (const command of commands) {
40
+ const group = command.path.length === 1 ? '' : command.path[0];
41
+ groups.set(group, [...(groups.get(group) ?? []), command]);
42
+ }
43
+ return [...groups.entries()]
44
+ .map(([group, members]) => {
45
+ const lines = members.map((command) => ` ${(0, output_1.pad)(command.path.join(' '), width)} ${command.summary}`);
46
+ return group === '' ? lines.join('\n') : [` ${group}`, ...lines].join('\n');
47
+ })
48
+ .join('\n\n');
49
+ }
50
+ function exitCodes() {
51
+ return [
52
+ 'Exit codes',
53
+ ...exit_codes_1.EXIT_CODE_TABLE.map((row) => ` ${row.code} ${(0, output_1.pad)(row.name, 9)} ${row.means}`),
54
+ ].join('\n');
55
+ }
56
+ /**
57
+ * `--help` as DATA, for `--json`.
58
+ *
59
+ * ⚠️ **This exists because `gf --json --help` printed the plain-text help to stdout** (cross-family
60
+ * review, Codex, round 2). `output.ts` states the contract in one line — under `--json`, stdout
61
+ * carries exactly one JSON document and nothing else — and the help path wrote straight to the
62
+ * writer, bypassing the emitter entirely. An agent that piped `gf --json --help` into a parser got
63
+ * a wall of prose.
64
+ *
65
+ * It is a STRUCTURED shape rather than `{ help: "<the same text>" }`, because the reason an agent
66
+ * asks for help is to learn the verbs and their flags — and a string forces it to parse the layout
67
+ * of a table it did not write. Both forms are pinned by golden files; changing either is a change
68
+ * to what every agent believes this tool can do.
69
+ */
70
+ function helpAsData(commands) {
71
+ return {
72
+ usage: 'gf <command> [flags]',
73
+ commands: commands.map((command) => ({
74
+ command: command.path.join(' '),
75
+ summary: command.summary,
76
+ usage: command.usage,
77
+ needsAuth: command.needsAuth,
78
+ flags: command.flags.map((flag) => ({
79
+ flag: `--${flag.name}`,
80
+ takesValue: flag.value !== undefined,
81
+ describe: flag.describe,
82
+ })),
83
+ })),
84
+ globalFlags: ['--json', '--project', '--api', '--token', '--help', '--version'],
85
+ environment: ['GOLDEN_FRIJOLES_TOKEN', 'GOLDEN_FRIJOLES_URL', 'GOLDEN_FRIJOLES_PROJECT'],
86
+ exitCodes: exit_codes_1.EXIT_CODE_TABLE.map((row) => ({ code: row.code, name: row.name, means: row.means })),
87
+ };
88
+ }
89
+ function commandAsData(command) {
90
+ return helpAsData([command]).commands[0];
91
+ }
92
+ function renderRootHelp(commands) {
93
+ return [
94
+ HEADER,
95
+ `Usage\n gf <command> [flags]`,
96
+ `Commands\n${verbList(commands)}`,
97
+ GLOBAL_FLAGS,
98
+ ENVIRONMENT,
99
+ exitCodes(),
100
+ ].join('\n\n');
101
+ }
102
+ function renderCommandHelp(command) {
103
+ const parts = [`gf ${command.path.join(' ')} — ${command.summary}`, `Usage\n ${command.usage}`];
104
+ if (command.detail)
105
+ parts.push(command.detail);
106
+ if (command.flags.length > 0) {
107
+ const width = Math.max(...command.flags.map((flag) => `--${flag.name}${flag.value ? ` ${flag.value}` : ''}`.length));
108
+ parts.push([
109
+ 'Flags',
110
+ ...command.flags.map((flag) => {
111
+ const left = `--${flag.name}${flag.value ? ` ${flag.value}` : ''}`;
112
+ return ` ${(0, output_1.pad)(left, width)} ${flag.describe}`;
113
+ }),
114
+ ].join('\n'));
115
+ }
116
+ parts.push(exitCodes());
117
+ return parts.join('\n\n');
118
+ }
@@ -0,0 +1,7 @@
1
+ export { run, type RunOptions } from './run';
2
+ export { COMMANDS } from './commands';
3
+ export { EXIT, EXIT_CODE_TABLE, exitForServerCode, type ExitCode } from './exit-codes';
4
+ export { parseArgs, type ParsedArgs } from './args';
5
+ export { renderCommandHelp, renderRootHelp } from './help';
6
+ export { VERSION } from './version';
7
+ export { DEFAULT_API_URL, CLI_TOKEN_FORMAT, credentialsPath, normalizeApiUrl, readCredentials, resolveAuth, writeCredentials, type Credentials, } from './credentials';
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeCredentials = exports.resolveAuth = exports.readCredentials = exports.normalizeApiUrl = exports.credentialsPath = exports.CLI_TOKEN_FORMAT = exports.DEFAULT_API_URL = exports.VERSION = exports.renderRootHelp = exports.renderCommandHelp = exports.parseArgs = exports.exitForServerCode = exports.EXIT_CODE_TABLE = exports.EXIT = exports.COMMANDS = exports.run = void 0;
4
+ // The programmatic surface. Small on purpose: this package is a BINARY, and everything exported
5
+ // here is something the repo's own tests or a future MCP bridge needs to reach.
6
+ var run_1 = require("./run");
7
+ Object.defineProperty(exports, "run", { enumerable: true, get: function () { return run_1.run; } });
8
+ var commands_1 = require("./commands");
9
+ Object.defineProperty(exports, "COMMANDS", { enumerable: true, get: function () { return commands_1.COMMANDS; } });
10
+ var exit_codes_1 = require("./exit-codes");
11
+ Object.defineProperty(exports, "EXIT", { enumerable: true, get: function () { return exit_codes_1.EXIT; } });
12
+ Object.defineProperty(exports, "EXIT_CODE_TABLE", { enumerable: true, get: function () { return exit_codes_1.EXIT_CODE_TABLE; } });
13
+ Object.defineProperty(exports, "exitForServerCode", { enumerable: true, get: function () { return exit_codes_1.exitForServerCode; } });
14
+ var args_1 = require("./args");
15
+ Object.defineProperty(exports, "parseArgs", { enumerable: true, get: function () { return args_1.parseArgs; } });
16
+ var help_1 = require("./help");
17
+ Object.defineProperty(exports, "renderCommandHelp", { enumerable: true, get: function () { return help_1.renderCommandHelp; } });
18
+ Object.defineProperty(exports, "renderRootHelp", { enumerable: true, get: function () { return help_1.renderRootHelp; } });
19
+ var version_1 = require("./version");
20
+ Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.VERSION; } });
21
+ var credentials_1 = require("./credentials");
22
+ Object.defineProperty(exports, "DEFAULT_API_URL", { enumerable: true, get: function () { return credentials_1.DEFAULT_API_URL; } });
23
+ Object.defineProperty(exports, "CLI_TOKEN_FORMAT", { enumerable: true, get: function () { return credentials_1.CLI_TOKEN_FORMAT; } });
24
+ Object.defineProperty(exports, "credentialsPath", { enumerable: true, get: function () { return credentials_1.credentialsPath; } });
25
+ Object.defineProperty(exports, "normalizeApiUrl", { enumerable: true, get: function () { return credentials_1.normalizeApiUrl; } });
26
+ Object.defineProperty(exports, "readCredentials", { enumerable: true, get: function () { return credentials_1.readCredentials; } });
27
+ Object.defineProperty(exports, "resolveAuth", { enumerable: true, get: function () { return credentials_1.resolveAuth; } });
28
+ Object.defineProperty(exports, "writeCredentials", { enumerable: true, get: function () { return credentials_1.writeCredentials; } });
@@ -0,0 +1,26 @@
1
+ export type Writer = {
2
+ out: (text: string) => void;
3
+ err: (text: string) => void;
4
+ };
5
+ /** The real one. Injected everywhere so the whole surface is assertable without capturing a process. */
6
+ export declare const processWriter: Writer;
7
+ export type Emitter = {
8
+ readonly json: boolean;
9
+ /** A successful result. Under `--json` this is the one document; otherwise `human` is printed. */
10
+ ok(payload: Record<string, unknown>, human: string): void;
11
+ /** A failure. `code` is the machine-readable reason; `message` is the sentence. */
12
+ fail(code: string, message: string, extra?: Record<string, unknown>): void;
13
+ /** Progress and asides. NEVER reaches stdout under `--json`. */
14
+ note(text: string): void;
15
+ };
16
+ export declare function createEmitter(writer: Writer, json: boolean): Emitter;
17
+ /** Left-pad a table column. No dependency, and no colour — see `--no-color`'s absence of an opposite. */
18
+ export declare function pad(text: string, width: number): string;
19
+ /**
20
+ * A plain-text table. Human mode only.
21
+ *
22
+ * Column widths are computed from the DATA, so a long flag key does not shear the row — the
23
+ * alternative (a fixed width with truncation) hides the end of exactly the identifiers a person is
24
+ * scanning for.
25
+ */
26
+ export declare function table(headers: readonly string[], rows: readonly (readonly string[])[]): string;
package/dist/output.js ADDED
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ // golden-frijoles-cli · Sprint 1, Story 1.1 — the two output modes, and the rule that separates
3
+ // them.
4
+ //
5
+ // ── The rule: `--json` writes ONE JSON document to stdout, and nothing else, ever ─────────────
6
+ // Not a progress line, not a warning, not a "minting…". An agent runs `gf … --json` and pipes
7
+ // stdout into a parser; one stray human sentence makes the whole document unparseable, and it does
8
+ // so intermittently — only on the runs that happened to warn. So under `--json`, stdout carries
9
+ // exactly one `JSON.stringify` and every human word goes to stderr or nowhere.
10
+ //
11
+ // ── Errors go to STDOUT under --json, and that is deliberate ──────────────────────────────────
12
+ // The reflex is stderr. It is wrong here: the machine-readable failure IS the result, and a caller
13
+ // who captured stdout and got an empty string cannot tell a failure from a command that produced
14
+ // nothing. The exit code says it failed; stdout says why, in the same shape as a success. Human
15
+ // mode keeps errors on stderr, where a human's shell expects them.
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.processWriter = void 0;
18
+ exports.createEmitter = createEmitter;
19
+ exports.pad = pad;
20
+ exports.table = table;
21
+ /** The real one. Injected everywhere so the whole surface is assertable without capturing a process. */
22
+ exports.processWriter = {
23
+ out: (text) => process.stdout.write(`${text}\n`),
24
+ err: (text) => process.stderr.write(`${text}\n`),
25
+ };
26
+ function createEmitter(writer, json) {
27
+ return {
28
+ json,
29
+ ok(payload, human) {
30
+ if (json)
31
+ writer.out(JSON.stringify({ ok: true, ...payload }, null, 2));
32
+ else
33
+ writer.out(human);
34
+ },
35
+ fail(code, message, extra) {
36
+ if (json)
37
+ writer.out(JSON.stringify({ ok: false, code, error: message, ...extra }, null, 2));
38
+ else
39
+ writer.err(message);
40
+ },
41
+ note(text) {
42
+ // Under `--json` a note is DROPPED, not redirected to stderr. Redirecting looks tidier and is
43
+ // worse: CI captures stderr too, and an agent asked to report the command's output would
44
+ // surface a progress line as though it were part of the answer.
45
+ if (!json)
46
+ writer.err(text);
47
+ },
48
+ };
49
+ }
50
+ /** Left-pad a table column. No dependency, and no colour — see `--no-color`'s absence of an opposite. */
51
+ function pad(text, width) {
52
+ return text.length >= width ? text : text + ' '.repeat(width - text.length);
53
+ }
54
+ /**
55
+ * A plain-text table. Human mode only.
56
+ *
57
+ * Column widths are computed from the DATA, so a long flag key does not shear the row — the
58
+ * alternative (a fixed width with truncation) hides the end of exactly the identifiers a person is
59
+ * scanning for.
60
+ */
61
+ function table(headers, rows) {
62
+ const widths = headers.map((header, column) => Math.max(header.length, ...rows.map((row) => (row[column] ?? '').length)));
63
+ const line = (cells) => cells
64
+ .map((cell, column) => pad(cell ?? '', widths[column]))
65
+ .join(' ')
66
+ .trimEnd();
67
+ return [line(headers), line(widths.map((width) => '─'.repeat(width))), ...rows.map(line)].join('\n');
68
+ }
package/dist/run.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { type Command } from './command';
2
+ import { type ExitCode } from './exit-codes';
3
+ import { type Writer } from './output';
4
+ export type RunOptions = {
5
+ argv: readonly string[];
6
+ writer?: Writer;
7
+ env?: NodeJS.ProcessEnv;
8
+ cwd?: string;
9
+ /** Injected in tests. Production passes nothing and the global `fetch` is used. */
10
+ fetchImpl?: typeof fetch;
11
+ commands?: readonly Command[];
12
+ };
13
+ export declare function run(options: RunOptions): Promise<ExitCode>;
package/dist/run.js ADDED
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ // golden-frijoles-cli · Sprint 1, Story 1.1 — the dispatcher.
3
+ //
4
+ // ── `run()` returns an exit code; only `bin.ts` calls `process.exit` ──────────────────────────
5
+ // So the whole CLI can be driven in-process by a test with a captured writer, an injected `fetch`
6
+ // and a temporary HOME — and asserted on its exact bytes and its exit code. A dispatcher that
7
+ // exited the process could only be tested by spawning one, which is how a CLI ends up with three
8
+ // assertions and a lot of confidence.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.run = run;
11
+ const args_1 = require("./args");
12
+ const api_1 = require("./api");
13
+ const command_1 = require("./command");
14
+ const commands_1 = require("./commands");
15
+ const credentials_1 = require("./credentials");
16
+ const exit_codes_1 = require("./exit-codes");
17
+ const help_1 = require("./help");
18
+ const output_1 = require("./output");
19
+ const version_1 = require("./version");
20
+ async function run(options) {
21
+ const writer = options.writer ?? output_1.processWriter;
22
+ const env = options.env ?? process.env;
23
+ const cwd = options.cwd ?? process.cwd();
24
+ const commands = options.commands ?? commands_1.COMMANDS;
25
+ const args = (0, args_1.parseArgs)(options.argv);
26
+ const emit = (0, output_1.createEmitter)(writer, args.json);
27
+ // `--version` before everything, including before the command table: someone asking which version
28
+ // they have is asking a question about the binary, not about a verb, and it must answer on a
29
+ // machine with no credential, no network and a typo in the command.
30
+ //
31
+ // ⚠️ **`args.path.length === 0` used to be part of this condition, and that made `--help`'s
32
+ // "global flags" section false** (cross-family review, Codex, PR #149). `gf whoami --version`
33
+ // entered `whoami`, demanded a credential and then made a network call — for a question about the
34
+ // binary. A flag documented as global is honoured globally, as `git --version` and `npm --version`
35
+ // are; the alternative is a help text an agent cannot trust, which is the whole of D5.
36
+ if (args.version) {
37
+ emit.ok({ version: version_1.VERSION }, version_1.VERSION);
38
+ return exit_codes_1.EXIT.OK;
39
+ }
40
+ if (args.path.length === 0 || args.path[0] === 'help') {
41
+ const target = args.path[0] === 'help' ? args.path.slice(1) : [];
42
+ if (target.length > 0) {
43
+ const found = (0, command_1.matchCommand)(commands, target);
44
+ if (!found)
45
+ return unknownCommand(target, emit);
46
+ // Through the EMITTER, not `writer.out`. Writing help directly bypassed `--json` entirely, so
47
+ // `gf --json help <verb>` emitted prose on the stdout an agent was parsing (Codex, round 2).
48
+ emit.ok({ help: (0, help_1.commandAsData)(found.command) }, (0, help_1.renderCommandHelp)(found.command));
49
+ return exit_codes_1.EXIT.OK;
50
+ }
51
+ emit.ok({ help: (0, help_1.helpAsData)(commands) }, (0, help_1.renderRootHelp)(commands));
52
+ // `gf` with no arguments prints help and exits 0 — it is what a person types to find out what
53
+ // this is, and answering their question is not an error. `gf nonsense` is (see below).
54
+ return exit_codes_1.EXIT.OK;
55
+ }
56
+ const found = (0, command_1.matchCommand)(commands, args.path);
57
+ if (!found)
58
+ return unknownCommand(args.path, emit);
59
+ const { command, rest } = found;
60
+ if (args.help) {
61
+ emit.ok({ help: (0, help_1.commandAsData)(command) }, (0, help_1.renderCommandHelp)(command));
62
+ return exit_codes_1.EXIT.OK;
63
+ }
64
+ // Leftover path words become positionals. `gf flags get checkout.demo` matches `['flags','get']`
65
+ // and leaves `checkout.demo`, which is how a verb receives its subject without the parser needing
66
+ // to know the arity of every command.
67
+ const withPositionals = { ...args, positionals: [...rest, ...args.positionals] };
68
+ // ⚠️ An unknown flag is a USAGE ERROR, not something to ignore. An agent that types `--environment`
69
+ // for `--env` and is silently ignored gets a flag created in the wrong place, with exit 0 — the
70
+ // CLI agreeing with a command nobody wrote.
71
+ const unknown = (0, args_1.unknownFlags)(withPositionals, command.flags.map((flag) => flag.name));
72
+ if (unknown.length > 0) {
73
+ emit.fail('invalid', `Unknown flag${unknown.length === 1 ? '' : 's'} for \`gf ${command.path.join(' ')}\`: ` +
74
+ `${unknown.map((name) => `--${name}`).join(', ')}. Run \`gf ${command.path.join(' ')} --help\`.`);
75
+ return exit_codes_1.EXIT.USAGE;
76
+ }
77
+ // ⚠️ **A flag supplied with an EMPTY value is a usage error — for every flag, not one** (cross-family
78
+ // review, Codex, round 6). `gf init --project "$UNSET_VAR"` expands to `--project ""`, and every
79
+ // resolver here is written `flagValue(...)?.trim() || <default>`, so the empty string is falsy and
80
+ // silently FELL BACK to the remembered project — minting a credential for a tenant the caller
81
+ // did not name, having visibly tried to name one. The reviewer found it on `--project`; the same
82
+ // `|| default` shape sits behind `--env`, `--api`, `--token` and `--reason`.
83
+ //
84
+ // Fixed at the parser boundary so the class is gone rather than one instance patched: a caller
85
+ // who typed a flag meant to supply it, and an empty value is never what they meant.
86
+ const blank = [...withPositionals.flags.entries()]
87
+ .filter(([, values]) => values.some((value) => value.trim() === ''))
88
+ .map(([name]) => `--${name}`)
89
+ .sort();
90
+ if (blank.length > 0) {
91
+ emit.fail('invalid', `${blank.join(', ')} ${blank.length === 1 ? 'was' : 'were'} given no value. An empty value is ` +
92
+ `refused rather than defaulted — check for an unset shell variable.`);
93
+ return exit_codes_1.EXIT.USAGE;
94
+ }
95
+ const auth = (0, credentials_1.resolveAuth)({
96
+ tokenFlag: (0, args_1.flagValue)(withPositionals, 'token'),
97
+ apiFlag: (0, args_1.flagValue)(withPositionals, 'api'),
98
+ env,
99
+ });
100
+ const clientFor = (token) => (0, api_1.createApiClient)({
101
+ baseUrl: auth.apiUrl,
102
+ token,
103
+ userAgent: `golden-frijoles-cli/${version_1.VERSION}`,
104
+ fetchImpl: options.fetchImpl,
105
+ });
106
+ if (command.needsAuth && auth.token === null) {
107
+ // ONE sentence, in one place, rather than every verb re-checking and phrasing it differently.
108
+ emit.fail('unauthorized', 'Not signed in. Run `gf login`, or set GOLDEN_FRIJOLES_TOKEN. `gf doctor` explains what it found.');
109
+ return exit_codes_1.EXIT.AUTH;
110
+ }
111
+ const context = {
112
+ args: withPositionals,
113
+ emit,
114
+ writer,
115
+ auth,
116
+ env,
117
+ cwd,
118
+ api: auth.token === null ? null : clientFor(auth.token),
119
+ clientFor,
120
+ fetchImpl: options.fetchImpl ?? fetch,
121
+ };
122
+ try {
123
+ return await command.run(context);
124
+ }
125
+ catch (err) {
126
+ // A handler that throws is a bug in this CLI, and it says so rather than printing a stack the
127
+ // caller cannot act on. EXIT.SERVER because the remedy — retry, then report — is the same.
128
+ emit.fail('server_error', `\`gf ${command.path.join(' ')}\` failed unexpectedly: ${err instanceof Error ? err.message : String(err)}`);
129
+ return exit_codes_1.EXIT.SERVER;
130
+ }
131
+ }
132
+ function unknownCommand(path, emit) {
133
+ emit.fail('invalid', `Unknown command \`gf ${path.join(' ')}\`. Run \`gf --help\` for the list.`);
134
+ return exit_codes_1.EXIT.USAGE;
135
+ }
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.1.0";
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ // The published version, as a constant.
5
+ //
6
+ // ⚠️ **Not `require('../package.json').version`.** The build emits `dist/` beside `package.json`, so
7
+ // the relative path differs between source and build, and a `resolveJsonModule` import would put a
8
+ // copy of the manifest into the bundle. Worse, it is exactly the kind of runtime lookup that
9
+ // silently returns `undefined` on a packaging change and prints `gf undefined`.
10
+ //
11
+ // It is a literal, and `version.test.ts` asserts it equals `package.json`'s — so the two cannot
12
+ // drift, and the drift is caught by the unit gate rather than by someone reading `gf --version`.
13
+ exports.VERSION = '0.1.0';
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@golden-frijoles/cli",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "description": "The Golden Frijoles CLI — create, roll out and kill feature flags from a terminal, or from an agent.",
6
+ "bin": {
7
+ "gf": "./dist/bin.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "keywords": [
19
+ "feature-flags",
20
+ "cli",
21
+ "golden-frijoles",
22
+ "agent"
23
+ ],
24
+ "license": "UNLICENSED",
25
+ "sideEffects": false,
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.build.json && node ./scripts/make-executable.mjs",
31
+ "typecheck": "tsc --noEmit",
32
+ "prepack": "npm run build"
33
+ },
34
+ "dependencies": {
35
+ "@golden-frijoles/sdk": "^0.5.0"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^5.5.4"
39
+ }
40
+ }