faces-cli 1.8.5 → 1.8.7

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/dist/style.d.ts CHANGED
@@ -24,12 +24,12 @@ export declare const STYLE_FACES_PATH = "/v1/voiceprint/faces";
24
24
  */
25
25
  export declare const DEFAULT_STYLE_MODEL = "gpt-5.6-terra";
26
26
  /**
27
- * Medium assumed for a room that does not declare one.
27
+ * Medium assumed for a room that declares nothing at all.
28
28
  *
29
- * Corpus rooms carry `corpus_kind`. Ordinary threads do not, because they are
30
- * live conversations rather than an imported corpus so a thread with no
31
- * declared medium is a conversation, which is the one case where the medium
32
- * follows from the source type rather than from a guess about content.
29
+ * Only live threads reach this. An imported corpus always reports a
30
+ * `corpus_medium`, so a room with none is a live conversation, and that is the
31
+ * one case where the medium follows from the source type rather than from a
32
+ * guess about content.
33
33
  */
34
34
  export declare const ROOM_DEFAULT_MEDIUM = "conversation";
35
35
  /** Exit code for "no such face", so a caller can tell it from a face with no style. */
@@ -48,8 +48,10 @@ export declare function faceExists(client: FacesClient, alias: string): Promise<
48
48
  export interface Selectable {
49
49
  id: string;
50
50
  sourceType: 'room' | 'document';
51
- /** Resolved from the listing. Undefined means the caller has to declare it. */
51
+ /** What the source itself declares. Undefined means nobody has said. */
52
52
  medium?: string;
53
+ /** A room with no imported corpus behind it, i.e. a live conversation. */
54
+ isLiveThread: boolean;
53
55
  label: string;
54
56
  synced: boolean;
55
57
  /**
@@ -67,10 +69,20 @@ export interface Selectable {
67
69
  printedAt?: string;
68
70
  printedMedium?: string;
69
71
  }
70
- /** What goes on the wire. `medium`, not `kind` — renamed 2026-08-27, no alias. */
72
+ /**
73
+ * What goes on the wire.
74
+ *
75
+ * `medium` is optional and is sent only when the caller chose one. Omitting it
76
+ * lets the server resolve from what the source already declares, which is
77
+ * strictly safer than resolving here: reading the wrong field once filed three
78
+ * corpus rooms as conversations, and a value we do not send cannot be wrong.
79
+ *
80
+ * `kind` was the old name and is refused rather than ignored, so there is no
81
+ * chance of sending it by accident.
82
+ */
71
83
  export interface SourceRef {
72
84
  id: string;
73
- medium: string;
85
+ medium?: string;
74
86
  source_type: 'room' | 'document';
75
87
  }
76
88
  /**
package/dist/style.js CHANGED
@@ -24,12 +24,12 @@ export const STYLE_FACES_PATH = '/v1/voiceprint/faces';
24
24
  */
25
25
  export const DEFAULT_STYLE_MODEL = 'gpt-5.6-terra';
26
26
  /**
27
- * Medium assumed for a room that does not declare one.
27
+ * Medium assumed for a room that declares nothing at all.
28
28
  *
29
- * Corpus rooms carry `corpus_kind`. Ordinary threads do not, because they are
30
- * live conversations rather than an imported corpus so a thread with no
31
- * declared medium is a conversation, which is the one case where the medium
32
- * follows from the source type rather than from a guess about content.
29
+ * Only live threads reach this. An imported corpus always reports a
30
+ * `corpus_medium`, so a room with none is a live conversation, and that is the
31
+ * one case where the medium follows from the source type rather than from a
32
+ * guess about content.
33
33
  */
34
34
  export const ROOM_DEFAULT_MEDIUM = 'conversation';
35
35
  /**
@@ -76,10 +76,13 @@ export async function faceExists(client, alias) {
76
76
  function mediumOf(r, sourceType) {
77
77
  if (sourceType === 'document')
78
78
  return r.medium?.trim() || undefined;
79
- const kind = r.corpus_kind?.trim();
80
- if (!kind)
81
- return ROOM_DEFAULT_MEDIUM;
82
- return kind === UNIDENTIFIED_CORPUS ? undefined : kind;
79
+ // A room carries two: `medium` is the user's correction, `corpus_medium` is
80
+ // what its messages arrived as. The correction wins, which is the same order
81
+ // the server resolves in.
82
+ const declared = r.medium?.trim() || r.corpus_medium?.trim();
83
+ // `unknown` is what an import reports when a message declared nothing. It is
84
+ // not a medium and the server refuses it, so it counts as undeclared here.
85
+ return !declared || declared === UNIDENTIFIED_CORPUS ? undefined : declared;
83
86
  }
84
87
  function rowToSelectable(r, sourceType) {
85
88
  const stamp = r.voiceprint ?? undefined;
@@ -91,7 +94,8 @@ function rowToSelectable(r, sourceType) {
91
94
  medium: mediumOf(r, sourceType),
92
95
  label: r.label?.trim() || '(untitled)',
93
96
  synced: Boolean(r.synced),
94
- isCorpus: sourceType === 'document' || Boolean(r.corpus_kind?.trim()),
97
+ isCorpus: sourceType === 'document' || Boolean(r.corpus_medium?.trim()),
98
+ isLiveThread: sourceType === 'room' && !r.corpus_medium?.trim(),
95
99
  authored: r.authored_message_count ?? undefined,
96
100
  // The stamp was renamed alongside the request field, but stored stamps kept
97
101
  // the old key (faces-backend-shared#589), so both are read.
@@ -171,9 +175,35 @@ export async function pollStyleJob(client, basePath, jobId, opts = {}) {
171
175
  return data;
172
176
  }
173
177
  }
178
+ /**
179
+ * Markers of a server-side fault that leaked its internals into the job error.
180
+ *
181
+ * A build failure is shown to the user, and a stack trace or a SQL statement
182
+ * tells them nothing they can act on while burying whatever might have. These
183
+ * are reported as an upstream fault with the first line kept for a bug report.
184
+ *
185
+ * The server sanitises `error` itself now (faces-backend-shared#623), so this
186
+ * is inert against everything it currently returns — checked against the
187
+ * sanitised text and against every deliberate code it documents
188
+ * (CHATGPT_AUTH, INSUFFICIENT_CREDITS, NOTHING_AUTHORED, HOLDOUT_UNMEETABLE,
189
+ * OAUTH_RATE_LIMITED), none of which match. It is kept as a backstop rather
190
+ * than removed: `error` is rendered straight to a user, a raw statement reached
191
+ * one once, and a check that costs nothing until it fires is worth more than
192
+ * the line it saves. It is deliberately narrow so an actionable message can
193
+ * never be swallowed by it.
194
+ */
195
+ const INTERNAL_FAULT = /sqlalchemy|asyncpg|psycopg|Traceback|\[SQL:|IntegrityError|OperationalError/i;
174
196
  /** Failure text, with the one hint that is actionable rather than descriptive. */
175
197
  export function jobFailureMessage(error) {
176
198
  const e = typeof error === 'string' && error ? error : 'unknown error';
199
+ if (INTERNAL_FAULT.test(e)) {
200
+ // Keep a short, quotable head so a bug report can name the fault, and drop
201
+ // the rest rather than printing a query at somebody.
202
+ const head = e.split('\n')[0].slice(0, 200);
203
+ return (`the server failed internally, so nothing was installed.\n${head}\n` +
204
+ 'This is a fault on the server, not something you did. Re-running is unlikely to help; ' +
205
+ 'report it with the job id from above.');
206
+ }
177
207
  if (e.toUpperCase().startsWith('CHATGPT_AUTH')) {
178
208
  return (`${e}\n` +
179
209
  'The ChatGPT link has expired. Reconnect it with: faces auth:connect openai\n' +
package/dist/utils.js CHANGED
@@ -95,7 +95,7 @@ export function isMaxTokensRenameError(message) {
95
95
  * synonyms fold server-side, the set grows, and compile's own 422 names the
96
96
  * valid ones better than a stale local list could.
97
97
  */
98
- export const MEDIUM_FLAG_DESCRIPTION = 'What sort of writing this is e.g. email, text message, social post, essay, academic paper, ' +
98
+ export const MEDIUM_FLAG_DESCRIPTION = 'What sort of writing this is, e.g. email, text message, social post, essay, academic paper, ' +
99
99
  'blog post, legal document, thread reply, conversation (dialogue: transcripts, interviews, calls), ' +
100
100
  'lecture (sustained speech nobody interrupts: talks, sermons, keynotes). Common synonyms fold ' +
101
101
  'automatically. Omit it if you do not know: a wrong declaration is worse than none, because a ' +
@@ -120,7 +120,7 @@ export function warnDroppedParams(headers, flagFor) {
120
120
  .map((p) => flagFor[p] ?? `--${p.replaceAll('_', '-')}`);
121
121
  if (flags.length === 0)
122
122
  return;
123
- process.stderr.write(`Note: ${flags.join(' and ')} had no effect this request ran on your linked ChatGPT account, ` +
123
+ process.stderr.write(`Note: ${flags.join(' and ')} had no effect. This request ran on your linked ChatGPT account, ` +
124
124
  'which does not accept them. Use --no-oauth-only to run on the paid API instead.\n');
125
125
  }
126
126
  /** Maps the server's parameter names back to the flags a user actually typed. */