faces-cli 1.5.14 → 1.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.
@@ -9,6 +9,7 @@ export default class ChatMessages extends BaseCommand {
9
9
  system: Flags.string({ description: 'System prompt' }),
10
10
  stream: Flags.boolean({ description: 'Stream the response', default: false }),
11
11
  'max-tokens': Flags.integer({ description: 'Max tokens', default: 1024 }),
12
+ 'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
12
13
  };
13
14
  static args = {
14
15
  face_model: Args.string({ description: 'Face model (e.g. myface or myface@claude-sonnet-4-6)', required: true }),
@@ -25,6 +26,8 @@ export default class ChatMessages extends BaseCommand {
25
26
  };
26
27
  if (flags.system)
27
28
  payload.system = flags.system;
29
+ if (flags['oauth-only'])
30
+ payload.oauth_only = true;
28
31
  try {
29
32
  if (flags.stream) {
30
33
  for await (const line of client.stream('/v1/messages', payload)) {
@@ -48,20 +51,36 @@ export default class ChatMessages extends BaseCommand {
48
51
  process.stdout.write('\n');
49
52
  return {};
50
53
  }
51
- const data = (await client.post('/v1/messages', { body: payload }));
52
- if (this.jsonEnabled())
53
- return data;
54
+ const { data, headers } = await client.postWithHeaders('/v1/messages', { body: payload });
55
+ const body = data;
56
+ if (this.jsonEnabled()) {
57
+ const meta = {};
58
+ if (headers['x-faces-provider'])
59
+ meta.provider = headers['x-faces-provider'];
60
+ if (headers['x-faces-cost-usd'])
61
+ meta.cost_usd = headers['x-faces-cost-usd'];
62
+ if (Object.keys(meta).length > 0)
63
+ body._meta = meta;
64
+ return body;
65
+ }
54
66
  let content = '';
55
- for (const block of data.content ?? []) {
67
+ for (const block of body.content ?? []) {
56
68
  if (block.type === 'text')
57
69
  content += String(block.text ?? '');
58
70
  }
59
71
  this.log(content);
60
- return data;
72
+ return body;
61
73
  }
62
74
  catch (err) {
63
- if (err instanceof FacesAPIError)
75
+ if (err instanceof FacesAPIError) {
76
+ if (err.errorCode === 'oauth_rejected') {
77
+ const hint = err.fallbackAvailable
78
+ ? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
79
+ : 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
80
+ this.error(`OAuth failed: ${err.message}\n${hint}`);
81
+ }
64
82
  this.error(`Error (${err.statusCode}): ${err.message}`);
83
+ }
65
84
  throw err;
66
85
  }
67
86
  }
@@ -5,6 +5,7 @@ export default class ChatResponses extends BaseCommand {
5
5
  message: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
6
6
  instructions: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
7
  stream: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ 'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
9
  'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
10
  token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -8,6 +8,7 @@ export default class ChatResponses extends BaseCommand {
8
8
  message: Flags.string({ char: 'm', description: 'User input message', required: true }),
9
9
  instructions: Flags.string({ description: 'System instructions' }),
10
10
  stream: Flags.boolean({ description: 'Stream the response', default: false }),
11
+ 'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
11
12
  };
12
13
  static args = {
13
14
  face_model: Args.string({ description: 'Face model (e.g. myface or myface@gpt-4o)', required: true }),
@@ -22,6 +23,8 @@ export default class ChatResponses extends BaseCommand {
22
23
  };
23
24
  if (flags.instructions)
24
25
  payload.instructions = flags.instructions;
26
+ if (flags['oauth-only'])
27
+ payload.oauth_only = true;
25
28
  try {
26
29
  if (flags.stream) {
27
30
  for await (const line of client.stream('/v1/responses', payload)) {
@@ -45,22 +48,38 @@ export default class ChatResponses extends BaseCommand {
45
48
  process.stdout.write('\n');
46
49
  return {};
47
50
  }
48
- const data = (await client.post('/v1/responses', { body: payload }));
49
- if (this.jsonEnabled())
50
- return data;
51
+ const { data, headers } = await client.postWithHeaders('/v1/responses', { body: payload });
52
+ const body = data;
53
+ if (this.jsonEnabled()) {
54
+ const meta = {};
55
+ if (headers['x-faces-provider'])
56
+ meta.provider = headers['x-faces-provider'];
57
+ if (headers['x-faces-cost-usd'])
58
+ meta.cost_usd = headers['x-faces-cost-usd'];
59
+ if (Object.keys(meta).length > 0)
60
+ body._meta = meta;
61
+ return body;
62
+ }
51
63
  let outputText = '';
52
- for (const item of data.output ?? []) {
64
+ for (const item of body.output ?? []) {
53
65
  for (const block of item.content ?? []) {
54
66
  if (block.type === 'output_text')
55
67
  outputText += String(block.text ?? '');
56
68
  }
57
69
  }
58
70
  this.log(outputText);
59
- return data;
71
+ return body;
60
72
  }
61
73
  catch (err) {
62
- if (err instanceof FacesAPIError)
74
+ if (err instanceof FacesAPIError) {
75
+ if (err.errorCode === 'oauth_rejected') {
76
+ const hint = err.fallbackAvailable
77
+ ? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
78
+ : 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
79
+ this.error(`OAuth failed: ${err.message}\n${hint}`);
80
+ }
63
81
  this.error(`Error (${err.statusCode}): ${err.message}`);
82
+ }
64
83
  throw err;
65
84
  }
66
85
  }
@@ -0,0 +1,11 @@
1
+ import { BaseCommand } from '../../base.js';
2
+ export default class CompileAll extends BaseCommand {
3
+ static description: string;
4
+ static flags: {
5
+ timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
6
+ 'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ run(): Promise<unknown>;
11
+ }
@@ -0,0 +1,116 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base.js';
3
+ import { FacesAPIError } from '../../client.js';
4
+ import { pollCompileProgress } from '../../poll.js';
5
+ export default class CompileAll extends BaseCommand {
6
+ static description = 'Compile all uncompiled documents and threads across all faces';
7
+ static flags = {
8
+ ...BaseCommand.baseFlags,
9
+ timeout: Flags.integer({ description: 'Per-item compile timeout in seconds (default: 600)', default: 600 }),
10
+ };
11
+ async run() {
12
+ const { flags } = await this.parse(CompileAll);
13
+ const client = this.makeClient(flags);
14
+ const json = this.jsonEnabled();
15
+ // Fetch all faces
16
+ let faces;
17
+ try {
18
+ const resp = await client.get('/v1/faces');
19
+ faces = (resp.data ?? resp);
20
+ }
21
+ catch (err) {
22
+ if (err instanceof FacesAPIError)
23
+ this.error(`Error (${err.statusCode}): ${err.message}`);
24
+ throw err;
25
+ }
26
+ // Collect uncompiled items
27
+ const items = [];
28
+ for (const face of faces) {
29
+ const alias = face.alias;
30
+ if (face.formula)
31
+ continue; // composite faces have no source material
32
+ // Check documents
33
+ try {
34
+ const docs = await client.get('/v1/compile/documents', { params: { alias } });
35
+ for (const doc of docs) {
36
+ const status = doc.prepare_status;
37
+ if (status !== 'synced' && status !== 'ready') {
38
+ items.push({
39
+ type: 'document',
40
+ id: doc.document_id,
41
+ alias,
42
+ label: doc.label ?? null,
43
+ });
44
+ }
45
+ }
46
+ }
47
+ catch { /* no docs */ }
48
+ // Check threads
49
+ try {
50
+ const threads = await client.get('/v1/compile/threads', { params: { alias } });
51
+ for (const thread of threads) {
52
+ const status = thread.prepare_status;
53
+ if (status !== 'synced' && status !== 'ready') {
54
+ items.push({
55
+ type: 'thread',
56
+ id: thread.thread_id,
57
+ alias,
58
+ label: thread.label ?? null,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ catch { /* no threads */ }
64
+ }
65
+ if (items.length === 0) {
66
+ if (!json)
67
+ this.log('Nothing to compile — all documents and threads are up to date.');
68
+ return { compiled: 0, failed: 0, items: [] };
69
+ }
70
+ if (!json)
71
+ this.log(`Found ${items.length} item(s) to compile\n`);
72
+ let compiled = 0;
73
+ let failed = 0;
74
+ const results = [];
75
+ for (let i = 0; i < items.length; i++) {
76
+ const item = items[i];
77
+ const tag = `[${i + 1}/${items.length}]`;
78
+ const name = item.label ?? item.id.slice(0, 8);
79
+ if (!json)
80
+ process.stderr.write(`${tag} ${item.type} "${name}" (${item.alias})... `);
81
+ const makeEndpoint = item.type === 'document'
82
+ ? `/v1/compile/documents/${item.id}/make`
83
+ : `/v1/compile/threads/${item.id}/make`;
84
+ const pollEndpoint = item.type === 'document'
85
+ ? `/v1/compile/documents/${item.id}`
86
+ : `/v1/compile/threads/${item.id}`;
87
+ try {
88
+ await client.post(makeEndpoint);
89
+ await pollCompileProgress(client, item.id, {
90
+ timeoutMs: flags.timeout * 1000,
91
+ endpoint: pollEndpoint,
92
+ onProgress: (p) => {
93
+ if (!json) {
94
+ process.stderr.write(`[${p.chunks_completed ?? '?'}/${p.chunks_total ?? '?'}] `);
95
+ }
96
+ },
97
+ });
98
+ compiled++;
99
+ results.push({ ...item, status: 'compiled' });
100
+ if (!json)
101
+ process.stderr.write('done\n');
102
+ }
103
+ catch (err) {
104
+ failed++;
105
+ const msg = err instanceof FacesAPIError ? `${err.statusCode}: ${err.message}` : err instanceof Error ? err.message : String(err);
106
+ results.push({ ...item, status: `failed: ${msg}` });
107
+ if (!json)
108
+ process.stderr.write(`failed: ${msg}\n`);
109
+ }
110
+ }
111
+ if (!json) {
112
+ this.log(`\nCompile complete: ${compiled} succeeded, ${failed} failed`);
113
+ }
114
+ return { compiled, failed, items: results };
115
+ }
116
+ }
@@ -3,6 +3,7 @@ export default class CompileThreadCreate extends BaseCommand {
3
3
  static description: string;
4
4
  static flags: {
5
5
  label: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
+ 'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
7
  'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
8
  token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -6,6 +6,7 @@ export default class CompileThreadCreate extends BaseCommand {
6
6
  static flags = {
7
7
  ...BaseCommand.baseFlags,
8
8
  label: Flags.string({ description: 'Thread label' }),
9
+ 'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
9
10
  };
10
11
  static args = {
11
12
  face_id: Args.string({ description: 'Face alias', required: true }),
@@ -16,13 +17,22 @@ export default class CompileThreadCreate extends BaseCommand {
16
17
  const payload = { alias: args.face_id };
17
18
  if (flags.label)
18
19
  payload.label = flags.label;
20
+ if (flags['oauth-only'])
21
+ payload.oauth_only = true;
19
22
  let data;
20
23
  try {
21
24
  data = await client.post('/v1/compile/threads', { body: payload });
22
25
  }
23
26
  catch (err) {
24
- if (err instanceof FacesAPIError)
27
+ if (err instanceof FacesAPIError) {
28
+ if (err.errorCode === 'oauth_rejected') {
29
+ const hint = err.fallbackAvailable
30
+ ? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
31
+ : 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
32
+ this.error(`OAuth failed: ${err.message}\n${hint}`);
33
+ }
25
34
  this.error(`Error (${err.statusCode}): ${err.message}`);
35
+ }
26
36
  throw err;
27
37
  }
28
38
  if (!this.jsonEnabled())
@@ -3,6 +3,7 @@ export default class CompileThreadMessage extends BaseCommand {
3
3
  static description: string;
4
4
  static flags: {
5
5
  message: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
6
+ 'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
7
  'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
8
  token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
9
  'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -6,6 +6,7 @@ export default class CompileThreadMessage extends BaseCommand {
6
6
  static flags = {
7
7
  ...BaseCommand.baseFlags,
8
8
  message: Flags.string({ char: 'm', description: 'User message to append', required: true }),
9
+ 'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
9
10
  };
10
11
  static args = {
11
12
  thread_id: Args.string({ description: 'Thread ID', required: true }),
@@ -13,15 +14,25 @@ export default class CompileThreadMessage extends BaseCommand {
13
14
  async run() {
14
15
  const { args, flags } = await this.parse(CompileThreadMessage);
15
16
  const client = this.makeClient(flags);
17
+ const payload = { message: flags.message };
18
+ if (flags['oauth-only'])
19
+ payload.oauth_only = true;
16
20
  let data;
17
21
  try {
18
22
  data = await client.post(`/v1/compile/threads/${args.thread_id}/messages`, {
19
- body: { message: flags.message },
23
+ body: payload,
20
24
  });
21
25
  }
22
26
  catch (err) {
23
- if (err instanceof FacesAPIError)
27
+ if (err instanceof FacesAPIError) {
28
+ if (err.errorCode === 'oauth_rejected') {
29
+ const hint = err.fallbackAvailable
30
+ ? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
31
+ : 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
32
+ this.error(`OAuth failed: ${err.message}\n${hint}`);
33
+ }
24
34
  this.error(`Error (${err.statusCode}): ${err.message}`);
35
+ }
25
36
  throw err;
26
37
  }
27
38
  if (!this.jsonEnabled())