faces-cli 1.7.9 → 1.7.14

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 (37) hide show
  1. package/dist/catalog.d.ts +2 -12
  2. package/dist/catalog.js +2 -3
  3. package/dist/commands/chat/chat.d.ts +1 -0
  4. package/dist/commands/chat/chat.js +36 -3
  5. package/dist/commands/chat/messages.d.ts +1 -0
  6. package/dist/commands/chat/messages.js +5 -1
  7. package/dist/commands/chat/responses.d.ts +1 -0
  8. package/dist/commands/chat/responses.js +5 -1
  9. package/dist/commands/chat/thread.d.ts +1 -0
  10. package/dist/commands/chat/thread.js +31 -3
  11. package/dist/commands/compile/doc/create.d.ts +1 -0
  12. package/dist/commands/compile/doc/create.js +4 -0
  13. package/dist/commands/compile/doc/edit.d.ts +1 -0
  14. package/dist/commands/compile/doc/edit.js +4 -0
  15. package/dist/commands/compile/doc/index.d.ts +1 -0
  16. package/dist/commands/compile/doc/index.js +7 -3
  17. package/dist/commands/compile/doc/make.js +1 -1
  18. package/dist/commands/compile/thread/make.js +1 -1
  19. package/dist/commands/compile/upload.d.ts +1 -0
  20. package/dist/commands/compile/upload.js +10 -1
  21. package/dist/commands/face/diff.js +8 -6
  22. package/dist/commands/face/list.d.ts +1 -0
  23. package/dist/commands/face/list.js +16 -3
  24. package/dist/commands/face/neighbors.js +20 -4
  25. package/dist/commands/face/publish.d.ts +15 -0
  26. package/dist/commands/face/publish.js +41 -0
  27. package/dist/commands/face/share.d.ts +21 -0
  28. package/dist/commands/face/share.js +106 -0
  29. package/dist/commands/face/unpublish.d.ts +13 -0
  30. package/dist/commands/face/unpublish.js +30 -0
  31. package/dist/commands/face/unshare.d.ts +17 -0
  32. package/dist/commands/face/unshare.js +67 -0
  33. package/dist/poll.d.ts +1 -6
  34. package/dist/utils.d.ts +34 -0
  35. package/dist/utils.js +57 -0
  36. package/oclif.manifest.json +920 -540
  37. package/package.json +1 -1
@@ -12,8 +12,8 @@ export default class FaceNeighbors extends BaseCommand {
12
12
  max: 20,
13
13
  }),
14
14
  component: Flags.string({
15
- description: 'Centroid component to rank on (beta, delta, epsilon are the principal components of a face; face is their composite)',
16
- options: ['face', 'beta', 'delta', 'epsilon'],
15
+ description: 'What to rank on: `face` for overall similarity, or a component position such as 1. ' +
16
+ 'Which positions are comparable varies; the API names them if you pick a wrong one.',
17
17
  default: 'face',
18
18
  }),
19
19
  direction: Flags.string({
@@ -33,19 +33,35 @@ export default class FaceNeighbors extends BaseCommand {
33
33
  component: flags.component,
34
34
  direction: flags.direction,
35
35
  };
36
+ // Positions are open-ended (they follow the component-counts order, which can
37
+ // grow), so validate the shape only and let the API say which are comparable
38
+ // — it knows, and the answer differs per face.
39
+ if (flags.component !== 'face' && !/^\d+$/.test(flags.component)) {
40
+ this.error(`--component must be 'face' or a component position such as 1 (got '${flags.component}').`);
41
+ }
36
42
  let data;
37
43
  try {
38
44
  data = await client.get(`/v1/faces/${args.face_id}/neighbors`, { params });
39
45
  }
40
46
  catch (err) {
41
- if (err instanceof FacesAPIError)
47
+ if (err instanceof FacesAPIError) {
48
+ // A face with nothing at that position is an empty answer, not a failure:
49
+ // it mirrors the null at the same index in face:diff. Say so plainly and
50
+ // exit clean, so a caller can tell "nothing here" from "something broke".
51
+ if (err.statusCode === 422 && /has no (data for component position|centroid)/.test(err.message)) {
52
+ if (!this.jsonEnabled())
53
+ this.log(err.message);
54
+ return { face: args.face_id, component: flags.component, direction: flags.direction, neighbors: [], reason: err.message };
55
+ }
42
56
  this.error(`Error (${err.statusCode}): ${err.message}`);
57
+ }
43
58
  throw err;
44
59
  }
45
60
  if (!this.jsonEnabled()) {
46
61
  const res = data;
47
62
  const label = res.direction === 'nearest' ? 'Nearest' : 'Most dissimilar';
48
- this.log(`${label} neighbors to ${res.face} (by ${res.component}):`);
63
+ const by = res.component === 'face' ? 'overall similarity' : `component ${res.component}`;
64
+ this.log(`${label} neighbors to ${res.face} (by ${by}):`);
49
65
  this.log('');
50
66
  if (res.neighbors.length === 0) {
51
67
  this.log(' (none)');
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../base.js';
2
+ export default class FacePublish extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ 'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ };
11
+ static args: {
12
+ alias: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
13
+ };
14
+ run(): Promise<unknown>;
15
+ }
@@ -0,0 +1,41 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ import { FacesAPIError } from '../../client.js';
4
+ export default class FacePublish extends BaseCommand {
5
+ static description = 'Publish a face so EVERY account can chat with it. This is a global override: while it is on, ' +
6
+ 'everyone can reach the face regardless of who it is shared with. You still pay only to compile it; ' +
7
+ 'whoever chats pays for their own inference.';
8
+ static examples = ['<%= config.bin %> <%= command.id %> alice --yes'];
9
+ static flags = {
10
+ ...BaseCommand.baseFlags,
11
+ yes: Flags.boolean({ description: 'Skip confirmation', default: false }),
12
+ };
13
+ static args = {
14
+ alias: Args.string({ description: 'Face alias', required: true }),
15
+ };
16
+ async run() {
17
+ const { args, flags } = await this.parse(FacePublish);
18
+ const client = this.makeClient(flags);
19
+ if (!flags.yes && !this.jsonEnabled()) {
20
+ this.error(`This makes '${args.alias}' chattable by every account on the platform. Re-run with --yes to confirm.`);
21
+ }
22
+ let data;
23
+ try {
24
+ data = (await client.patch(`/v1/faces/${encodeURIComponent(args.alias)}/publish`, {
25
+ body: { published: true },
26
+ }));
27
+ }
28
+ catch (err) {
29
+ if (err instanceof FacesAPIError)
30
+ this.error(`Error (${err.statusCode}): ${err.message}`);
31
+ throw err;
32
+ }
33
+ if (this.jsonEnabled())
34
+ return data;
35
+ this.log(`'${args.alias}' is published.`);
36
+ if (data.handle)
37
+ this.log(`Anyone can chat it as: ${data.handle}@<model>`);
38
+ this.log(`Unpublish with: faces face:unpublish ${args.alias}`);
39
+ return data;
40
+ }
41
+ }
@@ -0,0 +1,21 @@
1
+ import { BaseCommand } from '../../base.js';
2
+ export default class FaceShare extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ list: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ add: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ with: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ none: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ 'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ };
15
+ static args: {
16
+ alias: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
17
+ };
18
+ run(): Promise<unknown>;
19
+ private readList;
20
+ private report;
21
+ }
@@ -0,0 +1,106 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ import { FacesAPIError } from '../../client.js';
4
+ export default class FaceShare extends BaseCommand {
5
+ static description = 'Share a face so named accounts can chat with it. Sharing grants chat and nothing else: no reading its ' +
6
+ 'documents, no compiling, no re-sharing, and never its profile addendum. --add keeps the current list; ' +
7
+ '--with replaces it.';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %> alice --list',
10
+ '<%= config.bin %> <%= command.id %> alice --add dana',
11
+ '<%= config.bin %> <%= command.id %> alice --with dana --with sam@example.com',
12
+ '<%= config.bin %> <%= command.id %> alice --none --yes',
13
+ ];
14
+ static flags = {
15
+ ...BaseCommand.baseFlags,
16
+ list: Flags.boolean({ description: 'Show who the face is shared with, and change nothing', default: false }),
17
+ add: Flags.string({
18
+ description: 'Add an account, keeping everyone already on the list — username or email (repeatable)',
19
+ multiple: true,
20
+ exclusive: ['with', 'none'],
21
+ }),
22
+ with: Flags.string({
23
+ description: 'Set the list to exactly these accounts, dropping anyone else (repeatable)',
24
+ multiple: true,
25
+ exclusive: ['add', 'none'],
26
+ }),
27
+ none: Flags.boolean({ description: 'Revoke everyone', default: false, exclusive: ['add', 'with'] }),
28
+ yes: Flags.boolean({ description: 'Skip the confirmation shown when someone would lose access', default: false }),
29
+ };
30
+ static args = {
31
+ alias: Args.string({ description: 'Face alias', required: true }),
32
+ };
33
+ async run() {
34
+ const { args, flags } = await this.parse(FaceShare);
35
+ const client = this.makeClient(flags);
36
+ const json = this.jsonEnabled();
37
+ const current = await this.readList(client, args.alias);
38
+ if (flags.list) {
39
+ if (!json)
40
+ this.report(args.alias, current);
41
+ return { alias: args.alias, shared_with: current };
42
+ }
43
+ if (!flags.add && !flags.with && !flags.none) {
44
+ this.error('Provide --list, --add <account>, --with <account> (replaces), or --none.');
45
+ }
46
+ // The endpoint replaces rather than appends, so every form sends a full list.
47
+ // --add exists because "add one person" is the common intent and expressing it
48
+ // as a replace is the easiest way to revoke everyone else by accident.
49
+ let next;
50
+ if (flags.none)
51
+ next = [];
52
+ else if (flags.add)
53
+ next = [...new Set([...current, ...flags.add])];
54
+ else
55
+ next = [...new Set(flags.with)];
56
+ // Compare case-insensitively: stored values are canonical usernames, and a
57
+ // caller may well retype one with different capitalisation.
58
+ const lower = new Set(next.map((u) => u.toLowerCase()));
59
+ const removed = current.filter((u) => !lower.has(u.toLowerCase()));
60
+ if (removed.length > 0 && !flags.yes && !json) {
61
+ this.error(`This removes ${removed.length} account(s) from '${args.alias}': ${removed.join(', ')}.\n` +
62
+ 'Revocation is immediate. Re-run with --yes, or use --add to keep them.');
63
+ }
64
+ let data;
65
+ try {
66
+ data = (await client.patch(`/v1/faces/${encodeURIComponent(args.alias)}/share`, {
67
+ body: { shared_with: next },
68
+ }));
69
+ }
70
+ catch (err) {
71
+ // Unknown accounts are rejected atomically, so the previous list survives
72
+ // and the caller can fix the name and re-run.
73
+ if (err instanceof FacesAPIError)
74
+ this.error(`Error (${err.statusCode}): ${err.message}`);
75
+ throw err;
76
+ }
77
+ if (json)
78
+ return data;
79
+ if (removed.length > 0)
80
+ this.log(`Revoked: ${removed.join(', ')}`);
81
+ this.report(args.alias, data.shared_with ?? [], data.handle);
82
+ return data;
83
+ }
84
+ async readList(client, alias) {
85
+ try {
86
+ const face = (await client.get(`/v1/faces/${encodeURIComponent(alias)}`));
87
+ return face.shared_with ?? [];
88
+ }
89
+ catch (err) {
90
+ if (err instanceof FacesAPIError)
91
+ this.error(`Error (${err.statusCode}): ${err.message}`);
92
+ throw err;
93
+ }
94
+ }
95
+ report(alias, users, handle) {
96
+ if (users.length === 0) {
97
+ this.log(`'${alias}' is not shared with anyone.`);
98
+ return;
99
+ }
100
+ this.log(`'${alias}' is shared with ${users.length} account(s):`);
101
+ for (const u of users)
102
+ this.log(` ${u}`);
103
+ if (handle)
104
+ this.log(`\nThey chat with it as: ${handle}@<model>`);
105
+ }
106
+ }
@@ -0,0 +1,13 @@
1
+ import { BaseCommand } from '../../base.js';
2
+ export default class FaceUnpublish extends BaseCommand {
3
+ static description: string;
4
+ static flags: {
5
+ 'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ static args: {
10
+ alias: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
11
+ };
12
+ run(): Promise<unknown>;
13
+ }
@@ -0,0 +1,30 @@
1
+ import { Args } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ import { FacesAPIError } from '../../client.js';
4
+ export default class FaceUnpublish extends BaseCommand {
5
+ static description = 'Stop publishing a face. Anyone it is individually shared with keeps their access — publishing is an ' +
6
+ 'override on top of sharing, not a replacement for it.';
7
+ static flags = { ...BaseCommand.baseFlags };
8
+ static args = {
9
+ alias: Args.string({ description: 'Face alias', required: true }),
10
+ };
11
+ async run() {
12
+ const { args, flags } = await this.parse(FaceUnpublish);
13
+ const client = this.makeClient(flags);
14
+ let data;
15
+ try {
16
+ data = (await client.patch(`/v1/faces/${encodeURIComponent(args.alias)}/publish`, {
17
+ body: { published: false },
18
+ }));
19
+ }
20
+ catch (err) {
21
+ if (err instanceof FacesAPIError)
22
+ this.error(`Error (${err.statusCode}): ${err.message}`);
23
+ throw err;
24
+ }
25
+ if (this.jsonEnabled())
26
+ return data;
27
+ this.log(`'${args.alias}' is no longer published.`);
28
+ return data;
29
+ }
30
+ }
@@ -0,0 +1,17 @@
1
+ import { BaseCommand } from '../../base.js';
2
+ export default class FaceUnshare extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ from: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ 'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ static args: {
14
+ alias: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
15
+ };
16
+ run(): Promise<unknown>;
17
+ }
@@ -0,0 +1,67 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ import { FacesAPIError } from '../../client.js';
4
+ export default class FaceUnshare extends BaseCommand {
5
+ static description = 'Revoke access to a shared face. Takes effect immediately — there is no grace period.';
6
+ static examples = [
7
+ '<%= config.bin %> <%= command.id %> alice --from dana --yes',
8
+ '<%= config.bin %> <%= command.id %> alice --all --yes',
9
+ ];
10
+ static flags = {
11
+ ...BaseCommand.baseFlags,
12
+ from: Flags.string({ description: 'Account to revoke (repeatable)', multiple: true, exclusive: ['all'] }),
13
+ all: Flags.boolean({ description: 'Revoke everyone', default: false, exclusive: ['from'] }),
14
+ yes: Flags.boolean({ description: 'Skip confirmation', default: false }),
15
+ };
16
+ static args = {
17
+ alias: Args.string({ description: 'Face alias', required: true }),
18
+ };
19
+ async run() {
20
+ const { args, flags } = await this.parse(FaceUnshare);
21
+ const client = this.makeClient(flags);
22
+ const json = this.jsonEnabled();
23
+ if (!flags.from && !flags.all)
24
+ this.error('Provide --from <account> (repeatable) or --all.');
25
+ let current;
26
+ try {
27
+ const face = (await client.get(`/v1/faces/${encodeURIComponent(args.alias)}`));
28
+ current = face.shared_with ?? [];
29
+ }
30
+ catch (err) {
31
+ if (err instanceof FacesAPIError)
32
+ this.error(`Error (${err.statusCode}): ${err.message}`);
33
+ throw err;
34
+ }
35
+ if (current.length === 0) {
36
+ if (!json)
37
+ this.log(`'${args.alias}' is not shared with anyone.`);
38
+ return { alias: args.alias, shared_with: [] };
39
+ }
40
+ const drop = new Set((flags.from ?? []).map((u) => u.toLowerCase()));
41
+ const next = flags.all ? [] : current.filter((u) => !drop.has(u.toLowerCase()));
42
+ const removed = current.filter((u) => !next.includes(u));
43
+ if (removed.length === 0) {
44
+ // The list stores canonical usernames, so an email that granted access will
45
+ // not match here. Say that rather than reporting a bare "not found".
46
+ this.error(`None of those accounts are on '${args.alias}'. Currently shared with: ${current.join(', ')}.\n` +
47
+ 'The list stores usernames, so revoke by username even if you shared by email.');
48
+ }
49
+ if (!flags.yes && !json) {
50
+ this.error(`This revokes ${removed.join(', ')} from '${args.alias}', immediately. Re-run with --yes.`);
51
+ }
52
+ let data;
53
+ try {
54
+ data = await client.patch(`/v1/faces/${encodeURIComponent(args.alias)}/share`, { body: { shared_with: next } });
55
+ }
56
+ catch (err) {
57
+ if (err instanceof FacesAPIError)
58
+ this.error(`Error (${err.statusCode}): ${err.message}`);
59
+ throw err;
60
+ }
61
+ if (json)
62
+ return data;
63
+ this.log(`Revoked: ${removed.join(', ')}`);
64
+ this.log(next.length > 0 ? `Still shared with: ${next.join(', ')}` : `'${args.alias}' is no longer shared with anyone.`);
65
+ return data;
66
+ }
67
+ }
package/dist/poll.d.ts CHANGED
@@ -9,12 +9,7 @@ export interface PollProgress {
9
9
  prepare_status: string | null;
10
10
  chunks_total: number | null;
11
11
  chunks_completed: number | null;
12
- current_counts: {
13
- epsilon: number;
14
- beta: number;
15
- delta: number;
16
- alpha: number;
17
- } | null;
12
+ current_counts: number[] | null;
18
13
  }
19
14
  export interface PollOptions {
20
15
  intervalMs?: number;
package/dist/utils.d.ts CHANGED
@@ -28,3 +28,37 @@ export declare function renameFaceFields(face: Record<string, unknown>): Record<
28
28
  * - OpenAI Chat Completions: { choices: [{delta: {content}}] }
29
29
  */
30
30
  export declare function extractStreamDelta(parsed: Record<string, unknown>): string;
31
+ /**
32
+ * OpenAI's Responses API rejects an output cap below this. Checked client-side
33
+ * because the round trip tells the user nothing they could not be told sooner.
34
+ */
35
+ export declare const MIN_MAX_OUTPUT_TOKENS = 16;
36
+ /**
37
+ * True when a provider 400 is the `max_tokens` → `max_completion_tokens` rename
38
+ * that newer OpenAI models require.
39
+ *
40
+ * The backend forwards the provider's rejection verbatim rather than rewriting
41
+ * the parameter, so the client is what has to adapt. Matched on the message
42
+ * because the set of models that require it grows, and a model list here would
43
+ * be stale the day a new one ships.
44
+ */
45
+ export declare function isMaxTokensRenameError(message: string): boolean;
46
+ /**
47
+ * Shared help text for `--medium`. It declares what the artefact IS, not how it
48
+ * should sound — the persona decides that. Values are not validated here:
49
+ * synonyms fold server-side, the set grows, and compile's own 422 names the
50
+ * valid ones better than a stale local list could.
51
+ */
52
+ export declare const MEDIUM_FLAG_DESCRIPTION: string;
53
+ /**
54
+ * Warn when the server dropped request parameters before forwarding.
55
+ *
56
+ * The OAuth/Codex route strips params Codex rejects (`max_output_tokens`,
57
+ * `temperature`), so the same flag is honoured on the paid path and ignored on
58
+ * the free one — a difference the caller cannot see and did not choose. The
59
+ * server announces it in `x-faces-dropped-params`; without that header a client
60
+ * could only guess, since which route ran is decided per request.
61
+ */
62
+ export declare function warnDroppedParams(headers: Record<string, string>, flagFor: Record<string, string>): void;
63
+ /** Maps the server's parameter names back to the flags a user actually typed. */
64
+ export declare const CHAT_PARAM_FLAGS: Record<string, string>;
package/dist/utils.js CHANGED
@@ -71,3 +71,60 @@ export function extractStreamDelta(parsed) {
71
71
  const choices = parsed.choices;
72
72
  return String(choices?.[0]?.delta?.content ?? '');
73
73
  }
74
+ /**
75
+ * OpenAI's Responses API rejects an output cap below this. Checked client-side
76
+ * because the round trip tells the user nothing they could not be told sooner.
77
+ */
78
+ export const MIN_MAX_OUTPUT_TOKENS = 16;
79
+ /**
80
+ * True when a provider 400 is the `max_tokens` → `max_completion_tokens` rename
81
+ * that newer OpenAI models require.
82
+ *
83
+ * The backend forwards the provider's rejection verbatim rather than rewriting
84
+ * the parameter, so the client is what has to adapt. Matched on the message
85
+ * because the set of models that require it grows, and a model list here would
86
+ * be stale the day a new one ships.
87
+ */
88
+ export function isMaxTokensRenameError(message) {
89
+ return /max_completion_tokens/.test(message) && /max_tokens/.test(message);
90
+ }
91
+ /**
92
+ * Shared help text for `--medium`. It declares what the artefact IS, not how it
93
+ * should sound — the persona decides that. Values are not validated here:
94
+ * synonyms fold server-side, the set grows, and compile's own 422 names the
95
+ * valid ones better than a stale local list could.
96
+ */
97
+ export const MEDIUM_FLAG_DESCRIPTION = 'What sort of writing this is — e.g. email, text message, social post, essay, academic paper, ' +
98
+ 'blog post, legal document, thread reply, conversation (covers speech: transcripts, interviews, ' +
99
+ 'calls). Common synonyms fold automatically. Omit it if you do not know: a wrong declaration is ' +
100
+ 'worse than none, because a declaration is trusted and an absence is not.';
101
+ /**
102
+ * Warn when the server dropped request parameters before forwarding.
103
+ *
104
+ * The OAuth/Codex route strips params Codex rejects (`max_output_tokens`,
105
+ * `temperature`), so the same flag is honoured on the paid path and ignored on
106
+ * the free one — a difference the caller cannot see and did not choose. The
107
+ * server announces it in `x-faces-dropped-params`; without that header a client
108
+ * could only guess, since which route ran is decided per request.
109
+ */
110
+ export function warnDroppedParams(headers, flagFor) {
111
+ const raw = headers['x-faces-dropped-params'];
112
+ if (!raw)
113
+ return;
114
+ const flags = raw
115
+ .split(',')
116
+ .map((p) => p.trim())
117
+ .filter(Boolean)
118
+ .map((p) => flagFor[p] ?? `--${p.replaceAll('_', '-')}`);
119
+ if (flags.length === 0)
120
+ return;
121
+ process.stderr.write(`Note: ${flags.join(' and ')} had no effect — this request ran on your linked ChatGPT account, ` +
122
+ 'which does not accept them. Use --no-oauth-only to run on the paid API instead.\n');
123
+ }
124
+ /** Maps the server's parameter names back to the flags a user actually typed. */
125
+ export const CHAT_PARAM_FLAGS = {
126
+ max_output_tokens: '--max-tokens',
127
+ max_tokens: '--max-tokens',
128
+ max_completion_tokens: '--max-tokens',
129
+ temperature: '--temperature',
130
+ };