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/catalog.js +1 -1
- package/dist/commands/auth/connect.js +3 -3
- package/dist/commands/auth/connections.js +1 -1
- package/dist/commands/billing/subscription/activate.js +2 -2
- package/dist/commands/catalog/doctor.js +1 -1
- package/dist/commands/catalog/list.js +1 -1
- package/dist/commands/chat/thread.js +2 -2
- package/dist/commands/compile/all.js +1 -1
- package/dist/commands/compile/doc/index.js +3 -3
- package/dist/commands/compile/doc/pause.js +2 -2
- package/dist/commands/compile/import.js +1 -1
- package/dist/commands/compile/thread/edit.d.ts +3 -0
- package/dist/commands/compile/thread/edit.js +44 -9
- package/dist/commands/compile/thread/pause.js +2 -2
- package/dist/commands/face/diff.js +1 -1
- package/dist/commands/face/get.js +8 -0
- package/dist/commands/face/list.d.ts +1 -0
- package/dist/commands/face/list.js +10 -0
- package/dist/commands/face/lock.js +2 -2
- package/dist/commands/face/neighbors.js +1 -1
- package/dist/commands/face/share.js +1 -1
- package/dist/commands/face/sources.js +5 -2
- package/dist/commands/face/unlock.js +1 -1
- package/dist/commands/face/unpublish.js +1 -1
- package/dist/commands/face/unshare.js +1 -1
- package/dist/commands/keys/create.js +3 -3
- package/dist/commands/style/delete.js +13 -8
- package/dist/commands/style/make.js +40 -18
- package/dist/style.d.ts +20 -8
- package/dist/style.js +40 -10
- package/dist/utils.js +2 -2
- package/oclif.manifest.json +1177 -1147
- package/package.json +1 -1
package/dist/catalog.js
CHANGED
|
@@ -135,7 +135,7 @@ export class CatalogService {
|
|
|
135
135
|
// what kept it alive across writes; dropping it lets the next sync
|
|
136
136
|
// replace it with the real one.
|
|
137
137
|
if (existingDescription.length > MAX_FRONTMATTER_VALUE) {
|
|
138
|
-
process.stderr.write(`warn: discarded a ${existingDescription.length}-character description on '${alias}'
|
|
138
|
+
process.stderr.write(`warn: discarded a ${existingDescription.length}-character description on '${alias}': ` +
|
|
139
139
|
'too large to be real. It will be restored from the server on the next sync.\n');
|
|
140
140
|
}
|
|
141
141
|
else {
|
|
@@ -83,13 +83,13 @@ export default class AuthConnect extends BaseCommand {
|
|
|
83
83
|
// 3. Show the code + URL and the one-time enable-setting guidance.
|
|
84
84
|
emit('');
|
|
85
85
|
emit('To connect ChatGPT:');
|
|
86
|
-
emit(` 1. Open this URL in any browser (phone, laptop
|
|
86
|
+
emit(` 1. Open this URL in any browser (phone, laptop, anywhere):`);
|
|
87
87
|
emit(` ${verificationUri}`);
|
|
88
88
|
emit(` 2. Enter this code: ${start.user_code}`);
|
|
89
89
|
emit(` 3. Sign in to ChatGPT and click Continue.`);
|
|
90
90
|
emit('');
|
|
91
91
|
emit('If this is your first time, you may need to enable device-code authorization in ChatGPT first:');
|
|
92
|
-
emit(` 1. Open ${SETTINGS_URL} (the settings panel can take a few seconds to appear
|
|
92
|
+
emit(` 1. Open ${SETTINGS_URL} (the settings panel can take a few seconds to appear, so give it a moment)`);
|
|
93
93
|
emit(' 2. Turn on "Enable device code authorization for Codex"');
|
|
94
94
|
emit(' 3. Then enter the code above');
|
|
95
95
|
emit('');
|
|
@@ -193,7 +193,7 @@ export default class AuthConnect extends BaseCommand {
|
|
|
193
193
|
const steps = Array.isArray(d.settings_steps) && d.settings_steps.length > 0
|
|
194
194
|
? d.settings_steps
|
|
195
195
|
: [
|
|
196
|
-
`Open ${url} (the settings panel can take a few seconds to appear
|
|
196
|
+
`Open ${url} (the settings panel can take a few seconds to appear, so give it a moment)`,
|
|
197
197
|
'Turn on "Enable device code authorization for Codex"',
|
|
198
198
|
'Then run `faces auth:connect openai` again',
|
|
199
199
|
];
|
|
@@ -26,7 +26,7 @@ export default class AuthConnections extends BaseCommand {
|
|
|
26
26
|
const tier = label ? ` (${label})` : '';
|
|
27
27
|
this.log(`openai connected${who}${tier} connected_at=${row.connected_at}`);
|
|
28
28
|
if (!plan || plan === 'free') {
|
|
29
|
-
this.log(' ⚠️ This ChatGPT plan may not include API access
|
|
29
|
+
this.log(' ⚠️ This ChatGPT plan may not include API access. Connecting requires a paid plan (Plus/Pro/Team, etc.).');
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
@@ -46,7 +46,7 @@ export default class BillingSubscriptionActivate extends BaseCommand {
|
|
|
46
46
|
}
|
|
47
47
|
const result = data;
|
|
48
48
|
if (!json) {
|
|
49
|
-
this.log(`Reactivated. Connect plan continues
|
|
49
|
+
this.log(`Reactivated. Connect plan continues. Next renewal: ${result.current_period_end ?? 'unknown'}.`);
|
|
50
50
|
}
|
|
51
51
|
return { status: 'reactivated', ...result };
|
|
52
52
|
}
|
|
@@ -63,7 +63,7 @@ export default class BillingSubscriptionActivate extends BaseCommand {
|
|
|
63
63
|
const result = data;
|
|
64
64
|
const url = result.checkout_url;
|
|
65
65
|
if (!json) {
|
|
66
|
-
this.log('Subscription Connect
|
|
66
|
+
this.log('Subscription Connect: $17/month');
|
|
67
67
|
this.log('');
|
|
68
68
|
this.log('Complete payment at this link:');
|
|
69
69
|
this.log(` ${url}`);
|
|
@@ -301,7 +301,7 @@ export default class CatalogDoctor extends BaseCommand {
|
|
|
301
301
|
}
|
|
302
302
|
catch (err) {
|
|
303
303
|
const msg = err instanceof FacesAPIError ? `Error (${err.statusCode}): ${err.message}` : String(err);
|
|
304
|
-
this.log(` ${alias}: failed
|
|
304
|
+
this.log(` ${alias}: failed: ${msg}`);
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
this.log(`Generated ${generated} description(s).`);
|
|
@@ -19,7 +19,7 @@ export default class CatalogList extends BaseCommand {
|
|
|
19
19
|
if (this.jsonEnabled())
|
|
20
20
|
return entries;
|
|
21
21
|
if (entries.length === 0) {
|
|
22
|
-
this.log('(no faces in catalog
|
|
22
|
+
this.log('(no faces in catalog. Run faces catalog:doctor --fix)');
|
|
23
23
|
return entries;
|
|
24
24
|
}
|
|
25
25
|
const nameWidth = Math.max(...entries.map((e) => String(e.alias ?? '').length));
|
|
@@ -73,7 +73,7 @@ function listThreads() {
|
|
|
73
73
|
return out;
|
|
74
74
|
}
|
|
75
75
|
export default class ChatThread extends BaseCommand {
|
|
76
|
-
static description = 'Multi-turn chat thread. Conversation history is stored under ~/.faces/threads/<id>.json
|
|
76
|
+
static description = 'Multi-turn chat thread. Conversation history is stored under ~/.faces/threads/<id>.json. Resume any thread later with --id.';
|
|
77
77
|
static examples = [
|
|
78
78
|
'<%= config.bin %> <%= command.id %> socrates -m "What is justice?"',
|
|
79
79
|
'<%= config.bin %> <%= command.id %> --id t_abc123 -m "Say more about that"',
|
|
@@ -99,7 +99,7 @@ export default class ChatThread extends BaseCommand {
|
|
|
99
99
|
};
|
|
100
100
|
static args = {
|
|
101
101
|
face_username: Args.string({
|
|
102
|
-
description: 'Face alias (alias@model, or owner:alias@model for a published face)
|
|
102
|
+
description: 'Face alias (alias@model, or owner:alias@model for a published face). Required when starting a new thread',
|
|
103
103
|
required: false,
|
|
104
104
|
}),
|
|
105
105
|
};
|
|
@@ -64,7 +64,7 @@ export default class CompileAll extends BaseCommand {
|
|
|
64
64
|
}
|
|
65
65
|
if (items.length === 0) {
|
|
66
66
|
if (!json)
|
|
67
|
-
this.log('Nothing to compile
|
|
67
|
+
this.log('Nothing to compile. All documents and threads are up to date.');
|
|
68
68
|
return { compiled: 0, failed: 0, items: [] };
|
|
69
69
|
}
|
|
70
70
|
if (!json)
|
|
@@ -17,7 +17,7 @@ export default class CompileDoc extends BaseCommand {
|
|
|
17
17
|
...BaseCommand.baseFlags,
|
|
18
18
|
label: Flags.string({ description: 'Document label/title (single document only; with several files each is labelled by its filename)' }),
|
|
19
19
|
content: Flags.string({ description: 'Inline text content', exclusive: ['file'] }),
|
|
20
|
-
file: Flags.string({ description: 'Read content from file (repeatable
|
|
20
|
+
file: Flags.string({ description: 'Read content from file (repeatable; each file becomes its own document)', multiple: true }),
|
|
21
21
|
medium: Flags.string({ description: MEDIUM_FLAG_DESCRIPTION }),
|
|
22
22
|
perspective: Flags.string({
|
|
23
23
|
description: 'Perspective',
|
|
@@ -177,8 +177,8 @@ export default class CompileDoc extends BaseCommand {
|
|
|
177
177
|
}
|
|
178
178
|
/** Extensions the compile-documents endpoint takes as inline text. */
|
|
179
179
|
const BINARY_HINTS = {
|
|
180
|
-
'.pdf': 'PDF is supported, but not by this command
|
|
181
|
-
'.docx': 'Word documents are supported, but not by this command
|
|
180
|
+
'.pdf': 'PDF is supported, but not by this command. Use: faces compile:upload <alias> --file <path> --kind document',
|
|
181
|
+
'.docx': 'Word documents are supported, but not by this command. Use: faces compile:upload <alias> --file <path> --kind document',
|
|
182
182
|
'.doc': "Legacy '.doc' is not supported. Open it in Word or Pages and save as .docx, then upload that with: faces compile:upload <alias> --file <path> --kind document",
|
|
183
183
|
'.pages': 'Pages documents are not supported. Export it as text or PDF first.',
|
|
184
184
|
'.rtf': 'RTF is not supported. Convert it to plain text first (e.g. `textutil -convert txt file.rtf`).',
|
|
@@ -60,13 +60,13 @@ export default class CompileDocPause extends BaseCommand {
|
|
|
60
60
|
}
|
|
61
61
|
case 'synced':
|
|
62
62
|
case 'ready': {
|
|
63
|
-
return 'Compile finished before the pause took effect
|
|
63
|
+
return 'Compile finished before the pause took effect. Nothing to resume.';
|
|
64
64
|
}
|
|
65
65
|
case 'failed': {
|
|
66
66
|
return 'Compile failed before the pause took effect.';
|
|
67
67
|
}
|
|
68
68
|
case null: {
|
|
69
|
-
return 'Stopped
|
|
69
|
+
return 'Stopped. The compile is no longer running.';
|
|
70
70
|
}
|
|
71
71
|
default: {
|
|
72
72
|
return `Still stopping after ${timeout}s. Poll with: faces compile:doc:get ${id} --json`;
|
|
@@ -56,7 +56,7 @@ export default class CompileImport extends BaseCommand {
|
|
|
56
56
|
catch (err) {
|
|
57
57
|
if (err instanceof FacesAPIError) {
|
|
58
58
|
if (err.statusCode === 422 && flags.type === 'thread') {
|
|
59
|
-
this.error(`${err.message}
|
|
59
|
+
this.error(`${err.message}. Try again with --type document`);
|
|
60
60
|
}
|
|
61
61
|
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
62
62
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { BaseCommand } from '../../../base.js';
|
|
2
2
|
export default class CompileThreadEdit extends BaseCommand {
|
|
3
3
|
static description: string;
|
|
4
|
+
static examples: string[];
|
|
4
5
|
static flags: {
|
|
5
6
|
label: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
7
|
'face-speaker': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
medium: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'clear-medium': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
10
|
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
11
|
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
12
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { BaseCommand } from '../../../base.js';
|
|
3
3
|
import { FacesAPIError } from '../../../client.js';
|
|
4
|
+
import { MEDIUM_FLAG_DESCRIPTION } from '../../../utils.js';
|
|
4
5
|
export default class CompileThreadEdit extends BaseCommand {
|
|
5
|
-
static description = 'Edit a thread
|
|
6
|
+
static description = 'Edit a thread: its label, what medium it is read as, and/or which speaker is the face.';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> THREAD_ID --label "Interview"',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> THREAD_ID --medium lecture',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> THREAD_ID --clear-medium',
|
|
11
|
+
];
|
|
6
12
|
static flags = {
|
|
7
13
|
...BaseCommand.baseFlags,
|
|
8
14
|
label: Flags.string({ description: 'New thread label' }),
|
|
9
15
|
'face-speaker': Flags.string({
|
|
10
16
|
description: 'Reassign face speaker: set this speaker to role=user, all others to role=assistant',
|
|
11
17
|
}),
|
|
18
|
+
medium: Flags.string({
|
|
19
|
+
description: 'What this thread should be read as. ' +
|
|
20
|
+
MEDIUM_FLAG_DESCRIPTION +
|
|
21
|
+
' This is a correction that sticks: it overrides what an imported corpus arrived as, without ' +
|
|
22
|
+
'changing the record of what that was.',
|
|
23
|
+
exclusive: ['clear-medium'],
|
|
24
|
+
}),
|
|
25
|
+
'clear-medium': Flags.boolean({
|
|
26
|
+
description: 'Remove a medium correction, so the thread is read as whatever it arrived as.',
|
|
27
|
+
default: false,
|
|
28
|
+
exclusive: ['medium'],
|
|
29
|
+
}),
|
|
12
30
|
};
|
|
13
31
|
static args = {
|
|
14
32
|
thread_id: Args.string({ description: 'Thread ID', required: true }),
|
|
@@ -17,8 +35,8 @@ export default class CompileThreadEdit extends BaseCommand {
|
|
|
17
35
|
const { args, flags } = await this.parse(CompileThreadEdit);
|
|
18
36
|
const client = this.makeClient(flags);
|
|
19
37
|
const json = this.jsonEnabled();
|
|
20
|
-
if (!flags.label && !flags['face-speaker']) {
|
|
21
|
-
this.error('Provide at least one of --label or --face-speaker');
|
|
38
|
+
if (!flags.label && !flags['face-speaker'] && !flags.medium && !flags['clear-medium']) {
|
|
39
|
+
this.error('Provide at least one of --label, --medium, --clear-medium or --face-speaker');
|
|
22
40
|
}
|
|
23
41
|
// If --face-speaker, fetch messages, remap roles, PATCH back
|
|
24
42
|
if (flags['face-speaker']) {
|
|
@@ -64,18 +82,35 @@ export default class CompileThreadEdit extends BaseCommand {
|
|
|
64
82
|
process.stderr.write(`Remapped ${remapped} messages: "${speaker}" → user (${userCount}), all others → assistant (${assistantCount})\n`);
|
|
65
83
|
}
|
|
66
84
|
}
|
|
67
|
-
//
|
|
68
|
-
|
|
85
|
+
// Label and medium go in one partial patch. Null is meaningful for medium
|
|
86
|
+
// and distinct from absent, which is how a correction is cleared.
|
|
87
|
+
if (flags.label || flags.medium || flags['clear-medium']) {
|
|
88
|
+
const body = {};
|
|
89
|
+
if (flags.label)
|
|
90
|
+
body.label = flags.label;
|
|
91
|
+
if (flags.medium)
|
|
92
|
+
body.medium = flags.medium;
|
|
93
|
+
if (flags['clear-medium'])
|
|
94
|
+
body.medium = null;
|
|
69
95
|
try {
|
|
70
|
-
await client.patch(`/v1/compile/threads/${args.thread_id}`, { body
|
|
96
|
+
await client.patch(`/v1/compile/threads/${args.thread_id}`, { body });
|
|
71
97
|
}
|
|
72
98
|
catch (err) {
|
|
73
|
-
if (err instanceof FacesAPIError)
|
|
99
|
+
if (err instanceof FacesAPIError) {
|
|
100
|
+
// The derived value is read-only and the server names the field to
|
|
101
|
+
// send instead; pass that through rather than restating it.
|
|
74
102
|
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
103
|
+
}
|
|
75
104
|
throw err;
|
|
76
105
|
}
|
|
77
|
-
if (!json)
|
|
78
|
-
|
|
106
|
+
if (!json) {
|
|
107
|
+
if (flags.label)
|
|
108
|
+
process.stderr.write(`Label updated to "${flags.label}"\n`);
|
|
109
|
+
if (flags.medium)
|
|
110
|
+
process.stderr.write(`Now read as: ${flags.medium}\n`);
|
|
111
|
+
if (flags['clear-medium'])
|
|
112
|
+
process.stderr.write('Medium correction removed.\n');
|
|
113
|
+
}
|
|
79
114
|
}
|
|
80
115
|
// Fetch final state and return
|
|
81
116
|
let data;
|
|
@@ -61,13 +61,13 @@ export default class CompileThreadPause extends BaseCommand {
|
|
|
61
61
|
}
|
|
62
62
|
case 'synced':
|
|
63
63
|
case 'ready': {
|
|
64
|
-
return 'Compile finished before the pause took effect
|
|
64
|
+
return 'Compile finished before the pause took effect. Nothing to resume.';
|
|
65
65
|
}
|
|
66
66
|
case 'failed': {
|
|
67
67
|
return 'Compile failed before the pause took effect.';
|
|
68
68
|
}
|
|
69
69
|
case null: {
|
|
70
|
-
return 'Stopped
|
|
70
|
+
return 'Stopped. The compile is no longer running.';
|
|
71
71
|
}
|
|
72
72
|
default: {
|
|
73
73
|
return `Still stopping after ${timeout}s. Poll with: faces compile:thread:get ${id} --json`;
|
|
@@ -30,7 +30,7 @@ export default class FaceDiff extends BaseCommand {
|
|
|
30
30
|
if (!this.jsonEnabled()) {
|
|
31
31
|
const res = data;
|
|
32
32
|
const faces = res.faces;
|
|
33
|
-
const fmt = (v) => (v === null || v === undefined ? '
|
|
33
|
+
const fmt = (v) => (v === null || v === undefined ? '-' : v.toFixed(2));
|
|
34
34
|
for (let i = 0; i < faces.length; i++) {
|
|
35
35
|
for (let j = i + 1; j < faces.length; j++) {
|
|
36
36
|
const a = faces[i];
|
|
@@ -50,6 +50,14 @@ export default class FaceGet extends BaseCommand {
|
|
|
50
50
|
this.log(`address: ${resolved.handle}@<model> (chat only; it cannot join a team or be edited)`);
|
|
51
51
|
}
|
|
52
52
|
this.log(`created: ${new Date(f.created * 1000).toISOString().slice(0, 10)}`);
|
|
53
|
+
// Only report a print that can actually serve; null means none installed.
|
|
54
|
+
// The field is a list so it can grow; today `style` is its only member,
|
|
55
|
+
// so echoing it back as "style: style" would say nothing.
|
|
56
|
+
if (f.deepself?.length) {
|
|
57
|
+
const others = f.deepself.filter((k) => k !== 'style');
|
|
58
|
+
const extra = others.length > 0 ? `, ${others.join(', ')}` : '';
|
|
59
|
+
this.log(`style: installed${extra} (writes in its own voice)`);
|
|
60
|
+
}
|
|
53
61
|
if (f.read_only)
|
|
54
62
|
this.log('read only');
|
|
55
63
|
if (f.formula) {
|
|
@@ -6,6 +6,7 @@ export default class FaceList extends BaseCommand {
|
|
|
6
6
|
team: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
7
|
include: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
8
|
public: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
'has-style': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
10
|
shared: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
11
|
system: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
'from-users': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -12,6 +12,10 @@ export default class FaceList extends BaseCommand {
|
|
|
12
12
|
team: Flags.string({ description: 'Filter by team ID (repeatable, OR logic)', multiple: true }),
|
|
13
13
|
include: Flags.string({ description: 'Include extra fields: tags, teams (comma-separated)' }),
|
|
14
14
|
public: Flags.boolean({ description: 'Include published faces from other accounts (open to everybody)' }),
|
|
15
|
+
'has-style': Flags.boolean({
|
|
16
|
+
description: 'Show only faces with a captured style (the ones that write in their own voice)',
|
|
17
|
+
default: false,
|
|
18
|
+
}),
|
|
15
19
|
shared: Flags.boolean({ description: 'Include faces other accounts have shared with YOU in particular' }),
|
|
16
20
|
system: Flags.boolean({ description: `Show only the curated system faces (published faces owned by '${SYSTEM_OWNER}')` }),
|
|
17
21
|
'from-users': Flags.string({ description: 'Only published faces from these owners (repeatable/comma-separated)', multiple: true }),
|
|
@@ -70,6 +74,8 @@ export default class FaceList extends BaseCommand {
|
|
|
70
74
|
// set — the caller's own faces always come back. Filter client-side so
|
|
71
75
|
// --system / --from-users / --not-from-users actually mean "from these owners".
|
|
72
76
|
let faces = raw;
|
|
77
|
+
if (flags['has-style'])
|
|
78
|
+
faces = faces.filter(f => Boolean(f.deepself?.length));
|
|
73
79
|
if (fromUsers.length > 0) {
|
|
74
80
|
const allow = new Set(fromUsers);
|
|
75
81
|
faces = faces.filter(f => f.owned_by != null && allow.has(f.owned_by));
|
|
@@ -107,6 +113,10 @@ export default class FaceList extends BaseCommand {
|
|
|
107
113
|
const profile = f.profile_token_count ?? 0;
|
|
108
114
|
suffix = ` [profile: ${profile} tok, components: ${total}]`;
|
|
109
115
|
}
|
|
116
|
+
// A face with a captured style answers as the person rather than as a
|
|
117
|
+
// generic assistant, so it is worth seeing at a glance which do.
|
|
118
|
+
if (f.deepself?.length)
|
|
119
|
+
suffix += ` [${f.deepself.join(', ')}]`;
|
|
110
120
|
if (f.via?.length) {
|
|
111
121
|
const how = f.via.includes('shared') && f.via.includes('workspace')
|
|
112
122
|
? 'shared with you, and via a workspace'
|
|
@@ -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
|
|
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.';
|
|
6
6
|
static examples = ['<%= config.bin %> <%= command.id %> socrates'];
|
|
7
7
|
static flags = {
|
|
8
8
|
...BaseCommand.baseFlags,
|
|
@@ -24,7 +24,7 @@ export default class FaceLock extends BaseCommand {
|
|
|
24
24
|
}
|
|
25
25
|
if (!this.jsonEnabled()) {
|
|
26
26
|
const res = data;
|
|
27
|
-
this.log(`Locked '${res.alias ?? args.face_id}'
|
|
27
|
+
this.log(`Locked '${res.alias ?? args.face_id}' is now read only.`);
|
|
28
28
|
const affected = res.affected ?? [];
|
|
29
29
|
if (affected.length > 0)
|
|
30
30
|
this.log(`Affected: ${affected.join(', ')}`);
|
|
@@ -15,7 +15,7 @@ export default class FaceShare extends BaseCommand {
|
|
|
15
15
|
...BaseCommand.baseFlags,
|
|
16
16
|
list: Flags.boolean({ description: 'Show who the face is shared with, and change nothing', default: false }),
|
|
17
17
|
add: Flags.string({
|
|
18
|
-
description: 'Add an account, keeping everyone already on the list
|
|
18
|
+
description: 'Add an account, keeping everyone already on the list. Username or email (repeatable)',
|
|
19
19
|
multiple: true,
|
|
20
20
|
exclusive: ['with', 'none'],
|
|
21
21
|
}),
|
|
@@ -87,7 +87,9 @@ export default class FaceSources extends BaseCommand {
|
|
|
87
87
|
label: s.label?.trim() || '(untitled)',
|
|
88
88
|
tokens: s.token_count ?? 0,
|
|
89
89
|
status: this.statusOf(s),
|
|
90
|
-
|
|
90
|
+
// A thread carries a correction and a derived value; the correction wins,
|
|
91
|
+
// which is the order a style build resolves in.
|
|
92
|
+
medium: s.medium?.trim() || s.corpus_medium?.trim() || '-',
|
|
91
93
|
updated: (s.updated_at ?? s.created_at ?? '').slice(0, 10) || '-',
|
|
92
94
|
synced: Boolean(s.synced),
|
|
93
95
|
};
|
|
@@ -103,7 +105,8 @@ export default class FaceSources extends BaseCommand {
|
|
|
103
105
|
status: this.statusOf(s),
|
|
104
106
|
synced: Boolean(s.synced),
|
|
105
107
|
prepare_status: s.prepare_status ?? null,
|
|
106
|
-
medium: s.medium ?? null,
|
|
108
|
+
medium: s.medium ?? s.corpus_medium ?? null,
|
|
109
|
+
corpus_medium: s.corpus_medium ?? null,
|
|
107
110
|
created_at: s.created_at ?? null,
|
|
108
111
|
updated_at: s.updated_at ?? null,
|
|
109
112
|
read_only: Boolean(s.read_only),
|
|
@@ -24,7 +24,7 @@ export default class FaceUnlock extends BaseCommand {
|
|
|
24
24
|
}
|
|
25
25
|
if (!this.jsonEnabled()) {
|
|
26
26
|
const res = data;
|
|
27
|
-
this.log(`Unlocked '${res.alias ?? args.face_id}'
|
|
27
|
+
this.log(`Unlocked '${res.alias ?? args.face_id}' is now writable.`);
|
|
28
28
|
const affected = res.affected ?? [];
|
|
29
29
|
if (affected.length > 0)
|
|
30
30
|
this.log(`Affected: ${affected.join(', ')}`);
|
|
@@ -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 FaceUnpublish extends BaseCommand {
|
|
5
|
-
static description = 'Stop publishing a face. Anyone it is individually shared with keeps their access
|
|
5
|
+
static description = 'Stop publishing a face. Anyone it is individually shared with keeps their access. Publishing is an ' +
|
|
6
6
|
'override on top of sharing, not a replacement for it.';
|
|
7
7
|
static flags = { ...BaseCommand.baseFlags };
|
|
8
8
|
static args = {
|
|
@@ -2,7 +2,7 @@ import { Args, Flags } from '@oclif/core';
|
|
|
2
2
|
import { BaseCommand } from '../../base.js';
|
|
3
3
|
import { FacesAPIError } from '../../client.js';
|
|
4
4
|
export default class FaceUnshare extends BaseCommand {
|
|
5
|
-
static description = 'Revoke access to a shared face. Takes effect immediately
|
|
5
|
+
static description = 'Revoke access to a shared face. Takes effect immediately. There is no grace period.';
|
|
6
6
|
static examples = [
|
|
7
7
|
'<%= config.bin %> <%= command.id %> alice --from dana --yes',
|
|
8
8
|
'<%= config.bin %> <%= command.id %> alice --all --yes',
|
|
@@ -55,15 +55,15 @@ export default class KeysCreate extends BaseCommand {
|
|
|
55
55
|
}
|
|
56
56
|
this.printHuman(data);
|
|
57
57
|
this.log('');
|
|
58
|
-
this.log('⚠ The full key is shown only once
|
|
58
|
+
this.log('⚠ The full key is shown only once. `faces keys:list` returns it truncated by design.');
|
|
59
59
|
if (saved) {
|
|
60
|
-
this.log('✓ Saved to ~/.faces/config.json as api_key
|
|
60
|
+
this.log('✓ Saved to ~/.faces/config.json as api_key. Faces commands will use it automatically.');
|
|
61
61
|
}
|
|
62
62
|
else if (!flags.save) {
|
|
63
63
|
this.log(' Not saved (--no-save). Copy the key above now.');
|
|
64
64
|
}
|
|
65
65
|
else {
|
|
66
|
-
this.warn('Could not find the key in the response
|
|
66
|
+
this.warn('Could not find the key in the response. Nothing was saved. Copy it manually.');
|
|
67
67
|
}
|
|
68
68
|
return data;
|
|
69
69
|
}
|
|
@@ -4,15 +4,20 @@ import { BaseCommand } from '../../base.js';
|
|
|
4
4
|
import { FacesAPIError } from '../../client.js';
|
|
5
5
|
import { STYLE_FACES_PATH } from '../../style.js';
|
|
6
6
|
/**
|
|
7
|
-
* The API takes a scope
|
|
8
|
-
* destroys the uploaded material with it. This CLI only ever sends `map`.
|
|
7
|
+
* The API takes a scope. This CLI only ever sends `map`.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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.
|
|
16
21
|
*/
|
|
17
22
|
const SCOPE = 'map';
|
|
18
23
|
export default class StyleDelete extends BaseCommand {
|
|
@@ -2,7 +2,7 @@ import { Args, Flags } from '@oclif/core';
|
|
|
2
2
|
import { BaseCommand } from '../../base.js';
|
|
3
3
|
import { FacesAPIError } from '../../client.js';
|
|
4
4
|
import { codexEligible } from '../../routing.js';
|
|
5
|
-
import { BUILDS_PATH, DEFAULT_STYLE_MODEL, formatBuildReport, jobFailureMessage, listSelectable, parseSourceArg, pollStyleJob, publicJob, } from '../../style.js';
|
|
5
|
+
import { BUILDS_PATH, DEFAULT_STYLE_MODEL, ROOM_DEFAULT_MEDIUM, formatBuildReport, jobFailureMessage, listSelectable, parseSourceArg, pollStyleJob, publicJob, } from '../../style.js';
|
|
6
6
|
export default class StyleMake extends BaseCommand {
|
|
7
7
|
static description = 'Capture how a face writes, from its own material, and install it. This is style, not knowledge: ' +
|
|
8
8
|
'compile teaches a face what it knows, style teaches it how it sounds. Name the sources to learn ' +
|
|
@@ -173,14 +173,23 @@ export default class StyleMake extends BaseCommand {
|
|
|
173
173
|
else {
|
|
174
174
|
chosen = [];
|
|
175
175
|
const missing = [];
|
|
176
|
+
// Naming a source twice is accepted by the server and prints it twice,
|
|
177
|
+
// which weights it double in the analysis for no stated reason. A later
|
|
178
|
+
// mention wins so `--source X --source X:essay` declares a medium rather
|
|
179
|
+
// than being a contradiction.
|
|
180
|
+
const seen = new Map();
|
|
176
181
|
for (const raw of flags.source ?? []) {
|
|
177
182
|
const { id, medium } = parseSourceArg(raw);
|
|
178
|
-
|
|
179
|
-
if (s)
|
|
180
|
-
chosen.push({ s, declared: medium });
|
|
181
|
-
else
|
|
183
|
+
if (!byId.has(id)) {
|
|
182
184
|
missing.push(id);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (seen.has(id) && medium === undefined)
|
|
188
|
+
continue;
|
|
189
|
+
seen.set(id, medium ?? seen.get(id));
|
|
183
190
|
}
|
|
191
|
+
for (const [id, medium] of seen)
|
|
192
|
+
chosen.push({ s: byId.get(id), declared: medium });
|
|
184
193
|
if (missing.length > 0) {
|
|
185
194
|
this.error(`Not a source on '${alias}': ${missing.join(', ')}\n` +
|
|
186
195
|
`List what is there with: faces face:sources ${alias}`);
|
|
@@ -189,17 +198,30 @@ export default class StyleMake extends BaseCommand {
|
|
|
189
198
|
const undeclared = [];
|
|
190
199
|
const sources = [];
|
|
191
200
|
for (const { s, declared } of chosen) {
|
|
192
|
-
//
|
|
193
|
-
// itself
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
201
|
+
// A medium the source already declares is NOT sent. The server resolves it
|
|
202
|
+
// from the source itself, which cannot go stale the way our reading of its
|
|
203
|
+
// fields can — reading a renamed field once filed three corpus rooms as
|
|
204
|
+
// conversations. What we send is only ever what the caller chose.
|
|
205
|
+
//
|
|
206
|
+
// --medium fills gaps and must not override a source that declares one:
|
|
207
|
+
// resubmitting a document that says "essay" as something else leaves a
|
|
208
|
+
// stamp misreporting how it was read.
|
|
209
|
+
//
|
|
210
|
+
// A live thread declares nothing and is a conversation by construction, so
|
|
211
|
+
// that default comes last, after anything the caller asked for.
|
|
212
|
+
const chosenMedium = declared ?? (s.medium ? undefined : flags.medium);
|
|
213
|
+
if (chosenMedium) {
|
|
214
|
+
sources.push({ id: s.id, medium: chosenMedium, source_type: s.sourceType });
|
|
215
|
+
}
|
|
216
|
+
else if (s.medium) {
|
|
217
|
+
sources.push({ id: s.id, source_type: s.sourceType });
|
|
218
|
+
}
|
|
219
|
+
else if (s.isLiveThread) {
|
|
220
|
+
sources.push({ id: s.id, medium: ROOM_DEFAULT_MEDIUM, source_type: s.sourceType });
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
199
223
|
undeclared.push(s);
|
|
200
|
-
continue;
|
|
201
224
|
}
|
|
202
|
-
sources.push({ id: s.id, medium, source_type: s.sourceType });
|
|
203
225
|
}
|
|
204
226
|
if (undeclared.length > 0) {
|
|
205
227
|
const list = undeclared
|
|
@@ -210,10 +232,10 @@ export default class StyleMake extends BaseCommand {
|
|
|
210
232
|
' --medium email applies to every source that does not say',
|
|
211
233
|
` --source ${first.id}:email applies to just this one`,
|
|
212
234
|
];
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
235
|
+
// Both kinds can now record it permanently, through different commands.
|
|
236
|
+
ways.push(first.sourceType === 'document'
|
|
237
|
+
? ` faces compile:doc:edit ${first.id} --medium email records it permanently`
|
|
238
|
+
: ` faces compile:thread:edit ${first.id} --medium email records it permanently`);
|
|
217
239
|
this.error(`These sources do not say what sort of writing they are, and a style build will not guess:\n${list}\n\n` +
|
|
218
240
|
`Declare it, using the real medium rather than the example:\n${ways.join('\n')}\n\n` +
|
|
219
241
|
'A wrong declaration is worse than a missing one: a mislabelled source teaches the wrong voice ' +
|