faces-cli 1.8.7 → 1.8.8

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.
@@ -13,6 +13,7 @@ export default class ChatChat extends BaseCommand {
13
13
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
14
  responses: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
15
  medium: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ 'best-of': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
17
  'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
18
  'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
19
  token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -5,9 +5,12 @@ import { FacesAPIError } from '../../client.js';
5
5
  import { extractStreamDelta, isMaxTokensRenameError, MIN_MAX_OUTPUT_TOKENS, MEDIUM_FLAG_DESCRIPTION, warnDroppedParams, CHAT_PARAM_FLAGS } from '../../utils.js';
6
6
  import { resolveEndpoint, MESSAGES_ENDPOINT, RESPONSES_ENDPOINT, isCompositeFormula, faceSpecOf, llmFromArg } from '../../routing.js';
7
7
  export default class ChatChat extends BaseCommand {
8
- static description = 'Chat via a face. Auto-routes to the correct API endpoint based on the model catalog.';
8
+ static description = 'Chat via a face. Auto-routes to the correct API endpoint based on the model catalog. ' +
9
+ 'Prefix the alias with + to answer in the style the face captured, e.g. +alice; without it the face ' +
10
+ 'uses its ordinary voice.';
9
11
  static examples = [
10
12
  '<%= config.bin %> <%= command.id %> socrates -m "What is virtue?"',
13
+ '<%= config.bin %> <%= command.id %> +alice -m "Reply to the note below." --medium email',
11
14
  '<%= config.bin %> <%= command.id %> "(socrates | nietzsche)@claude-sonnet-4-6" -m "What is virtue?"',
12
15
  '<%= config.bin %> <%= command.id %> --formula "socrates | nietzsche" --llm claude-sonnet-4-6 -m "What is virtue?"',
13
16
  ];
@@ -23,11 +26,16 @@ export default class ChatChat extends BaseCommand {
23
26
  file: Flags.string({ description: 'Read message from file' }),
24
27
  responses: Flags.boolean({ description: 'Force the OpenAI Responses API endpoint (escape hatch; routing is automatic otherwise)', default: false }),
25
28
  medium: Flags.string({ description: MEDIUM_FLAG_DESCRIPTION }),
29
+ 'best-of': Flags.integer({
30
+ description: 'Write N replies and serve the one closest to the captured style (1-5, default 1). Only ' +
31
+ 'meaningful with a + alias, and it multiplies the cost of the request by N. Cannot be streamed.',
32
+ }),
26
33
  'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
27
34
  };
28
35
  static args = {
29
36
  face_username: Args.string({
30
- description: 'Face alias (alias@model, owner:alias@model for a published face, or a composite formula like "(a | b)@model"). Omit when using --formula.',
37
+ description: 'Face alias (alias@model, owner:alias@model for a published face, or a composite formula like ' +
38
+ '"(a | b)@model"). Prefix with + to use the face\'s captured style. Omit when using --formula.',
31
39
  required: false,
32
40
  }),
33
41
  };
@@ -59,6 +67,20 @@ export default class ChatChat extends BaseCommand {
59
67
  this.error(`Composite face '${spec}' has no default model. Specify one explicitly, ` +
60
68
  `e.g. '${spec}@claude-sonnet-4-6', or pass --llm <model>.`);
61
69
  }
70
+ // best_of_n picks between finished drafts, so there is nothing to stream.
71
+ // The server answers 400 deepself_streaming_unsupported; say so first.
72
+ if (flags['best-of'] !== undefined) {
73
+ if (flags['best-of'] < 1 || flags['best-of'] > 5) {
74
+ this.error(`--best-of must be between 1 and 5 (got ${flags['best-of']}).`);
75
+ }
76
+ if (flags.stream) {
77
+ this.error('--best-of cannot be streamed: choosing between drafts needs them finished. Drop --stream or --best-of.');
78
+ }
79
+ if (!model.startsWith('+')) {
80
+ this.error(`--best-of only applies to a face's captured style, so the alias needs a + prefix: '+${model}'.\n` +
81
+ 'Without it the face answers in its ordinary voice and there is nothing to choose between.');
82
+ }
83
+ }
62
84
  // Collect user messages
63
85
  const userMessages = [];
64
86
  if (flags.file) {
@@ -90,6 +112,14 @@ export default class ChatChat extends BaseCommand {
90
112
  if (err instanceof FacesAPIError) {
91
113
  if (err.errorCode === 'oauth_rejected')
92
114
  this.error(`OAuth failed: ${err.message}\n${this.oauthHint(err, { ...route, endpoint })}`);
115
+ // A + request on a face with no captured style is refused, not quietly
116
+ // downgraded. The same request without the + works.
117
+ if (err.statusCode === 409 && model.startsWith('+')) {
118
+ const bare = faceSpecOf(model).replace(/^\+/, '');
119
+ this.error(`Error (409): ${err.message}\n` +
120
+ `Capture one with: faces style:make ${bare} --all\n` +
121
+ `Or drop the + to use ${bare}'s ordinary voice.`);
122
+ }
93
123
  this.error(`Error (${err.statusCode}): ${err.message}`);
94
124
  }
95
125
  throw err;
@@ -143,6 +173,8 @@ export default class ChatChat extends BaseCommand {
143
173
  payload.temperature = Number.parseFloat(flags.temperature);
144
174
  if (flags.medium)
145
175
  payload.medium = flags.medium;
176
+ if (flags['best-of'] !== undefined)
177
+ payload.best_of_n = flags['best-of'];
146
178
  if (flags['oauth-only'])
147
179
  payload.oauth_only = true;
148
180
  if (flags.stream) {
@@ -189,6 +221,8 @@ export default class ChatChat extends BaseCommand {
189
221
  payload.system = flags.system;
190
222
  if (flags.medium)
191
223
  payload.medium = flags.medium;
224
+ if (flags['best-of'] !== undefined)
225
+ payload.best_of_n = flags['best-of'];
192
226
  if (flags['oauth-only'])
193
227
  payload.oauth_only = true;
194
228
  if (flags.stream) {
@@ -234,6 +268,8 @@ export default class ChatChat extends BaseCommand {
234
268
  payload.temperature = Number.parseFloat(flags.temperature);
235
269
  if (flags.medium)
236
270
  payload.medium = flags.medium;
271
+ if (flags['best-of'] !== undefined)
272
+ payload.best_of_n = flags['best-of'];
237
273
  if (flags['oauth-only'])
238
274
  payload.oauth_only = true;
239
275
  if (flags.stream) {
@@ -2,7 +2,7 @@ import { Args } from '@oclif/core';
2
2
  import { BaseCommand } from '../../base.js';
3
3
  import { FacesAPIError } from '../../client.js';
4
4
  export default class FaceLock extends BaseCommand {
5
- static description = 'Lock a face (read-only). A locked face still reads and chats normally, but it, and everything it owns (documents, threads, voiceprint), cannot be changed until unlocked.';
5
+ static description = 'Lock a face (read-only). A locked face still reads and chats normally, but it, and everything it owns (documents, threads, captured style), cannot be changed until unlocked.';
6
6
  static examples = ['<%= config.bin %> <%= command.id %> socrates'];
7
7
  static flags = {
8
8
  ...BaseCommand.baseFlags,
@@ -2,7 +2,7 @@ import { Args } from '@oclif/core';
2
2
  import { BaseCommand } from '../../base.js';
3
3
  import { FacesAPIError } from '../../client.js';
4
4
  export default class FaceUnlock extends BaseCommand {
5
- static description = 'Unlock a face (make it writable again). Thaws the face and everything it owns (documents, threads, voiceprint).';
5
+ static description = 'Unlock a face (make it writable again). Thaws the face and everything it owns (documents, threads, captured style).';
6
6
  static examples = ['<%= config.bin %> <%= command.id %> socrates'];
7
7
  static flags = {
8
8
  ...BaseCommand.baseFlags,
@@ -3,23 +3,6 @@ import { createInterface } from 'node:readline';
3
3
  import { BaseCommand } from '../../base.js';
4
4
  import { FacesAPIError } from '../../client.js';
5
5
  import { STYLE_FACES_PATH } from '../../style.js';
6
- /**
7
- * The API takes a scope. This CLI only ever sends `map`.
8
- *
9
- * `map` removes the installed style. `all` removes it and everything derived
10
- * from it — versions, jobs, checkpoints, style exemplars. **Neither touches the
11
- * uploaded material.** The API reference said `all` destroyed it; that
12
- * documentation was wrong and has been corrected, and the behaviour is not
13
- * going to change (backend memo, 2026-08-28 pm).
14
- *
15
- * So the reason for sending only `map` is no longer that `all` is dangerous to
16
- * source text. It is that one command should do one thing: this one forgets a
17
- * style, and a deeper purge of derived artefacts is a different request nobody
18
- * has asked for. Source text is deleted through the commands that own it,
19
- * compile:doc:delete and compile:thread:delete, and a face's uploaded corpus
20
- * through the corpus route — never as a side effect of a flag value here.
21
- */
22
- const SCOPE = 'map';
23
6
  export default class StyleDelete extends BaseCommand {
24
7
  static description = "Delete a face's captured style. The material it was learned from is kept, so the style can be " +
25
8
  'captured again without re-uploading. This never deletes documents or threads: remove those with ' +
@@ -48,7 +31,7 @@ export default class StyleDelete extends BaseCommand {
48
31
  }
49
32
  let data;
50
33
  try {
51
- data = await client.delete(`${STYLE_FACES_PATH}/${encodeURIComponent(args.alias)}?scope=${SCOPE}`);
34
+ data = await client.delete(`${STYLE_FACES_PATH}/${encodeURIComponent(args.alias)}`);
52
35
  }
53
36
  catch (err) {
54
37
  if (err instanceof FacesAPIError) {
@@ -104,7 +104,11 @@ export default class StyleMake extends BaseCommand {
104
104
  for (const line of formatBuildReport(final))
105
105
  this.log(line);
106
106
  this.log('');
107
- this.log(`'${args.alias}' now writes in its own style. Try it: faces chat:chat ${args.alias} -m "..."`);
107
+ // Capturing a style does not apply it. The plain alias still answers in the
108
+ // face's ordinary voice; the + prefix is how a caller asks for the style.
109
+ this.log(`'${args.alias}' has captured a style. Use it by prefixing the alias with +:`);
110
+ this.log(` faces chat:chat +${args.alias} -m "..."`);
111
+ this.log(`Without the +, the face answers in its ordinary voice. Both are valid.`);
108
112
  this.log(`Go back to the previous style: faces style:revert ${args.alias} --yes`);
109
113
  return final;
110
114
  }
package/dist/style.d.ts CHANGED
@@ -11,9 +11,9 @@
11
11
  * other from what the list endpoints already report.
12
12
  */
13
13
  import { FacesClient } from './client.js';
14
- export declare const BUILDS_PATH = "/v1/voiceprint/builds";
15
- export declare const VERSIONS_PATH = "/v1/voiceprint/versions";
16
- export declare const STYLE_FACES_PATH = "/v1/voiceprint/faces";
14
+ export declare const BUILDS_PATH = "/v1/style/builds";
15
+ export declare const VERSIONS_PATH = "/v1/style/versions";
16
+ export declare const STYLE_FACES_PATH = "/v1/style/faces";
17
17
  /**
18
18
  * Analyst model used when the caller does not choose one.
19
19
  *
package/dist/style.js CHANGED
@@ -11,9 +11,9 @@
11
11
  * other from what the list endpoints already report.
12
12
  */
13
13
  import { FacesAPIError } from './client.js';
14
- export const BUILDS_PATH = '/v1/voiceprint/builds';
15
- export const VERSIONS_PATH = '/v1/voiceprint/versions';
16
- export const STYLE_FACES_PATH = '/v1/voiceprint/faces';
14
+ export const BUILDS_PATH = '/v1/style/builds';
15
+ export const VERSIONS_PATH = '/v1/style/versions';
16
+ export const STYLE_FACES_PATH = '/v1/style/faces';
17
17
  /**
18
18
  * Analyst model used when the caller does not choose one.
19
19
  *
@@ -85,7 +85,7 @@ function mediumOf(r, sourceType) {
85
85
  return !declared || declared === UNIDENTIFIED_CORPUS ? undefined : declared;
86
86
  }
87
87
  function rowToSelectable(r, sourceType) {
88
- const stamp = r.voiceprint ?? undefined;
88
+ const stamp = r.style ?? undefined;
89
89
  return {
90
90
  id: String(r.document_id ?? r.thread_id ?? ''),
91
91
  sourceType,
@@ -97,10 +97,8 @@ function rowToSelectable(r, sourceType) {
97
97
  isCorpus: sourceType === 'document' || Boolean(r.corpus_medium?.trim()),
98
98
  isLiveThread: sourceType === 'room' && !r.corpus_medium?.trim(),
99
99
  authored: r.authored_message_count ?? undefined,
100
- // The stamp was renamed alongside the request field, but stored stamps kept
101
- // the old key (faces-backend-shared#589), so both are read.
102
- printedMedium: stamp?.medium ?? stamp?.kind,
103
- printedAt: stamp?.printed_at,
100
+ printedMedium: stamp?.medium,
101
+ printedAt: stamp?.captured_at,
104
102
  };
105
103
  }
106
104
  function rowsOf(data) {