faces-cli 1.6.1 → 1.6.2
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/README.md +50 -17
- package/dist/client.d.ts +4 -0
- package/dist/client.js +16 -0
- package/dist/commands/catalog/backup.js +84 -19
- package/dist/commands/catalog/doctor.js +10 -4
- package/dist/commands/catalog/restore.js +64 -11
- package/dist/commands/face/create.d.ts +1 -0
- package/dist/commands/face/create.js +14 -1
- package/dist/commands/face/list.d.ts +1 -0
- package/dist/commands/face/list.js +11 -1
- package/dist/commands/face/tag/add.d.ts +14 -0
- package/dist/commands/face/tag/add.js +29 -0
- package/dist/commands/face/tag/list.d.ts +13 -0
- package/dist/commands/face/tag/list.js +35 -0
- package/dist/commands/face/tag/remove.d.ts +14 -0
- package/dist/commands/face/tag/remove.js +29 -0
- package/dist/commands/face/tag/set.d.ts +14 -0
- package/dist/commands/face/tag/set.js +29 -0
- package/dist/commands/face/update.d.ts +1 -0
- package/dist/commands/face/update.js +17 -6
- package/dist/commands/team/add.d.ts +14 -0
- package/dist/commands/team/add.js +51 -0
- package/dist/commands/team/create.d.ts +15 -0
- package/dist/commands/team/create.js +53 -0
- package/dist/commands/team/delete.d.ts +15 -0
- package/dist/commands/team/delete.js +44 -0
- package/dist/commands/team/get.d.ts +13 -0
- package/dist/commands/team/get.js +28 -0
- package/dist/commands/team/list.d.ts +10 -0
- package/dist/commands/team/list.js +49 -0
- package/dist/commands/team/members.d.ts +13 -0
- package/dist/commands/team/members.js +28 -0
- package/dist/commands/team/remove.d.ts +14 -0
- package/dist/commands/team/remove.js +42 -0
- package/dist/commands/team/tag/add.d.ts +14 -0
- package/dist/commands/team/tag/add.js +29 -0
- package/dist/commands/team/tag/list.d.ts +13 -0
- package/dist/commands/team/tag/list.js +35 -0
- package/dist/commands/team/tag/remove.d.ts +14 -0
- package/dist/commands/team/tag/remove.js +29 -0
- package/dist/commands/team/tag/set.d.ts +14 -0
- package/dist/commands/team/tag/set.js +29 -0
- package/dist/commands/team/update.d.ts +17 -0
- package/dist/commands/team/update.js +48 -0
- package/dist/team-catalog.d.ts +16 -0
- package/dist/team-catalog.js +99 -0
- package/oclif.manifest.json +1522 -436
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# faces-cli
|
|
2
2
|
|
|
3
|
-
Command-line interface for the [Faces](https://faces.sh) platform.
|
|
3
|
+
Command-line interface for the [Faces](https://faces.sh) platform. Compile source material into AI personas with cognitive primitives, then chat with them, compare them, and compose them.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,43 +8,76 @@ Command-line interface for the [Faces](https://faces.sh) platform.
|
|
|
8
8
|
npm install -g faces-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Requires Node.js
|
|
11
|
+
Requires Node.js >= 18.
|
|
12
12
|
|
|
13
13
|
## Quick start
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
17
|
-
faces
|
|
18
|
-
faces
|
|
16
|
+
# Register or login
|
|
17
|
+
faces auth:register --email you@example.com --password 'YourPass123!' --username you
|
|
18
|
+
faces auth:login --email you@example.com --password 'YourPass123!'
|
|
19
|
+
|
|
20
|
+
# Create a face
|
|
21
|
+
faces face:create --name "Alice" --alias alice --default-model gpt-5.4-mini \
|
|
22
|
+
--attr gender=female --attr age=29 --attr occupation="product designer"
|
|
23
|
+
|
|
24
|
+
# Add source material and compile
|
|
25
|
+
faces compile:doc alice --file essay.txt --no-wait
|
|
26
|
+
faces compile:upload alice --file interview.mp3 --kind thread --no-wait
|
|
27
|
+
|
|
28
|
+
# Chat
|
|
29
|
+
faces chat:chat alice -m "What matters most to you?"
|
|
30
|
+
faces chat:chat alice -m "Tell me more" --llm claude-sonnet-4-6
|
|
31
|
+
|
|
32
|
+
# Compare and compose
|
|
33
|
+
faces face:diff --face alice --face bob
|
|
34
|
+
faces face:create --name "Alice-Bob" --alias alice-bob --formula "alice | bob"
|
|
19
35
|
```
|
|
20
36
|
|
|
21
37
|
## Commands
|
|
22
38
|
|
|
23
39
|
```
|
|
24
|
-
faces auth
|
|
25
|
-
faces
|
|
26
|
-
|
|
27
|
-
faces
|
|
28
|
-
faces
|
|
29
|
-
|
|
30
|
-
faces
|
|
31
|
-
|
|
32
|
-
faces
|
|
40
|
+
faces auth:login / logout / register / whoami / refresh
|
|
41
|
+
faces auth:connect / disconnect / connections
|
|
42
|
+
|
|
43
|
+
faces face:create / list / get / update / delete / stats / attributes
|
|
44
|
+
faces face:upload / diff / neighbors
|
|
45
|
+
|
|
46
|
+
faces chat:chat / messages / responses
|
|
47
|
+
|
|
48
|
+
faces compile:doc / compile:doc:create / list / get / edit / make / pause / reset / delete
|
|
49
|
+
faces compile:thread:create / list / get / edit / message / make / pause / reset / sync / delete
|
|
50
|
+
faces compile:upload / compile:import
|
|
51
|
+
faces compile:all
|
|
52
|
+
|
|
53
|
+
faces catalog:list / doctor / backup / restore / manyfaced
|
|
54
|
+
|
|
55
|
+
faces keys:create / list / revoke / update
|
|
56
|
+
|
|
57
|
+
faces billing:balance / subscription / quota / usage / topup / checkout / card-setup / llm-costs
|
|
58
|
+
|
|
59
|
+
faces account:state / preferences
|
|
60
|
+
|
|
61
|
+
faces config:set / show / clear
|
|
33
62
|
```
|
|
34
63
|
|
|
35
64
|
Run `faces <command> --help` for details on any command.
|
|
36
65
|
|
|
37
66
|
## Authentication
|
|
38
67
|
|
|
39
|
-
Login stores a JWT at `~/.faces/config.json
|
|
40
|
-
You can also authenticate via environment variables:
|
|
68
|
+
Login stores a JWT at `~/.faces/config.json`. You can also authenticate via environment variables:
|
|
41
69
|
|
|
42
70
|
```bash
|
|
43
|
-
export FACES_API_KEY=
|
|
71
|
+
export FACES_API_KEY=sk-faces-...
|
|
44
72
|
export FACES_TOKEN=your_jwt
|
|
45
73
|
export FACES_BASE_URL=https://api.faces.sh # optional override
|
|
46
74
|
```
|
|
47
75
|
|
|
76
|
+
## Plans
|
|
77
|
+
|
|
78
|
+
- **Pay-per-token** — $5 activation (added as credits), 5% markup on all API calls. No monthly fee.
|
|
79
|
+
- **Subscription Connect** ($17/month) — same 5% markup, plus link your ChatGPT Plus/Pro account to use select OpenAI models at no extra cost for compilation and inference.
|
|
80
|
+
|
|
48
81
|
## License
|
|
49
82
|
|
|
50
83
|
MIT — Copyright (c) 2026 Headwaters Lab Inc.
|
package/dist/client.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ export declare class FacesClient {
|
|
|
33
33
|
body?: unknown;
|
|
34
34
|
}): Promise<ResponseWithHeaders>;
|
|
35
35
|
postNoAuth(path: string, body: unknown): Promise<unknown>;
|
|
36
|
+
put(path: string, opts?: {
|
|
37
|
+
requireJwt?: boolean;
|
|
38
|
+
body?: unknown;
|
|
39
|
+
}): Promise<unknown>;
|
|
36
40
|
patch(path: string, opts?: {
|
|
37
41
|
requireJwt?: boolean;
|
|
38
42
|
body?: unknown;
|
package/dist/client.js
CHANGED
|
@@ -141,6 +141,22 @@ export class FacesClient {
|
|
|
141
141
|
throw await this.parseError(resp);
|
|
142
142
|
return resp.json();
|
|
143
143
|
}
|
|
144
|
+
async put(path, opts = {}) {
|
|
145
|
+
let resp;
|
|
146
|
+
try {
|
|
147
|
+
resp = await fetch(this.url(path), {
|
|
148
|
+
method: 'PUT',
|
|
149
|
+
headers: { ...this.authHeader(opts.requireJwt), 'Content-Type': 'application/json' },
|
|
150
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
this.wrapNetworkError(err, path);
|
|
155
|
+
}
|
|
156
|
+
if (!resp.ok)
|
|
157
|
+
throw await this.parseError(resp);
|
|
158
|
+
return resp.json();
|
|
159
|
+
}
|
|
144
160
|
async patch(path, opts = {}) {
|
|
145
161
|
let resp;
|
|
146
162
|
try {
|
|
@@ -5,7 +5,7 @@ import { BaseCommand } from '../../base.js';
|
|
|
5
5
|
import { FacesAPIError } from '../../client.js';
|
|
6
6
|
import { flattenBasicFacts } from '../../utils.js';
|
|
7
7
|
export default class CatalogBackup extends BaseCommand {
|
|
8
|
-
static description = 'Snapshot all faces and source material for migration';
|
|
8
|
+
static description = 'Snapshot all faces, teams, and source material for migration';
|
|
9
9
|
static flags = {
|
|
10
10
|
...BaseCommand.baseFlags,
|
|
11
11
|
};
|
|
@@ -31,13 +31,28 @@ export default class CatalogBackup extends BaseCommand {
|
|
|
31
31
|
const alias = face.alias;
|
|
32
32
|
if (!json)
|
|
33
33
|
process.stderr.write(` ${alias}: `);
|
|
34
|
+
// Fetch full face details (list endpoint may omit description)
|
|
35
|
+
let fullFace = face;
|
|
36
|
+
try {
|
|
37
|
+
fullFace = await client.get(`/v1/faces/${alias}`);
|
|
38
|
+
}
|
|
39
|
+
catch { /* use list data */ }
|
|
40
|
+
// Fetch tags
|
|
41
|
+
let tags = [];
|
|
42
|
+
try {
|
|
43
|
+
const tagResp = await client.get(`/v1/iam/${alias}/tags`);
|
|
44
|
+
tags = tagResp.tags ?? [];
|
|
45
|
+
}
|
|
46
|
+
catch { /* no tags */ }
|
|
34
47
|
const entry = {
|
|
35
48
|
alias,
|
|
36
|
-
name:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
name: fullFace.name,
|
|
50
|
+
description: fullFace.description ?? null,
|
|
51
|
+
basic_facts: fullFace.basic_facts ? flattenBasicFacts(fullFace.basic_facts) : null,
|
|
52
|
+
default_model: fullFace.default_model ?? null,
|
|
53
|
+
default_tools: fullFace.default_tools ?? [],
|
|
54
|
+
formula: fullFace.formula ?? null,
|
|
55
|
+
tags,
|
|
41
56
|
documents: [],
|
|
42
57
|
threads: [],
|
|
43
58
|
};
|
|
@@ -52,9 +67,7 @@ export default class CatalogBackup extends BaseCommand {
|
|
|
52
67
|
});
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
|
-
catch {
|
|
56
|
-
// face may have no documents
|
|
57
|
-
}
|
|
70
|
+
catch { /* face may have no documents */ }
|
|
58
71
|
// Fetch threads
|
|
59
72
|
try {
|
|
60
73
|
const threads = await client.get('/v1/compile/threads', { params: { alias } });
|
|
@@ -68,22 +81,74 @@ export default class CatalogBackup extends BaseCommand {
|
|
|
68
81
|
messages,
|
|
69
82
|
});
|
|
70
83
|
}
|
|
71
|
-
catch {
|
|
72
|
-
// skip threads we can't fetch
|
|
73
|
-
}
|
|
84
|
+
catch { /* skip threads we can't fetch */ }
|
|
74
85
|
}
|
|
75
86
|
}
|
|
76
|
-
catch {
|
|
77
|
-
// face may have no threads
|
|
78
|
-
}
|
|
87
|
+
catch { /* face may have no threads */ }
|
|
79
88
|
if (!json)
|
|
80
|
-
process.stderr.write(`${entry.documents.length} doc(s), ${entry.threads.length} thread(s)\n`);
|
|
89
|
+
process.stderr.write(`${entry.documents.length} doc(s), ${entry.threads.length} thread(s), ${tags.length} tag(s)\n`);
|
|
81
90
|
backupFaces.push(entry);
|
|
82
91
|
}
|
|
92
|
+
// Fetch teams
|
|
93
|
+
const backupTeams = [];
|
|
94
|
+
let username;
|
|
95
|
+
try {
|
|
96
|
+
const resp = await client.get('/auth/me');
|
|
97
|
+
const whoami = (resp.data ?? resp);
|
|
98
|
+
username = whoami.username ?? undefined;
|
|
99
|
+
}
|
|
100
|
+
catch { /* proceed */ }
|
|
101
|
+
try {
|
|
102
|
+
const resp = await client.get('/v1/me/teams');
|
|
103
|
+
let teams = resp.data ?? resp;
|
|
104
|
+
if (username)
|
|
105
|
+
teams = teams.filter(t => t.name !== username);
|
|
106
|
+
for (const team of teams) {
|
|
107
|
+
const teamId = team.id;
|
|
108
|
+
if (!json)
|
|
109
|
+
process.stderr.write(` team ${team.name}: `);
|
|
110
|
+
// Fetch members
|
|
111
|
+
let memberAliases = [];
|
|
112
|
+
try {
|
|
113
|
+
const membersResp = await client.get(`/v1/teams/${teamId}/members`);
|
|
114
|
+
const members = membersResp.data ?? [];
|
|
115
|
+
// Build uid→alias map from original face list
|
|
116
|
+
const uidToAlias = new Map();
|
|
117
|
+
for (const f of faces) {
|
|
118
|
+
if (f.uid)
|
|
119
|
+
uidToAlias.set(f.uid, f.alias);
|
|
120
|
+
}
|
|
121
|
+
for (const m of members) {
|
|
122
|
+
const iamId = m.iam_id;
|
|
123
|
+
const alias = uidToAlias.get(iamId) ?? iamId;
|
|
124
|
+
memberAliases.push(alias);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch { /* no members */ }
|
|
128
|
+
// Fetch tags
|
|
129
|
+
let teamTags = [];
|
|
130
|
+
try {
|
|
131
|
+
const tagResp = await client.get(`/v1/teams/${teamId}/tags`);
|
|
132
|
+
teamTags = tagResp.tags ?? [];
|
|
133
|
+
}
|
|
134
|
+
catch { /* no tags */ }
|
|
135
|
+
backupTeams.push({
|
|
136
|
+
name: team.name,
|
|
137
|
+
description: team.description ?? null,
|
|
138
|
+
protocol: team.protocol ?? null,
|
|
139
|
+
tags: teamTags,
|
|
140
|
+
members: memberAliases,
|
|
141
|
+
});
|
|
142
|
+
if (!json)
|
|
143
|
+
process.stderr.write(`${memberAliases.length} member(s), ${teamTags.length} tag(s)\n`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch { /* no teams */ }
|
|
83
147
|
const backup = {
|
|
84
|
-
version:
|
|
148
|
+
version: 2,
|
|
85
149
|
created_at: new Date().toISOString(),
|
|
86
150
|
faces: backupFaces,
|
|
151
|
+
teams: backupTeams,
|
|
87
152
|
};
|
|
88
153
|
const backupDir = path.join(os.homedir(), '.faces', 'backups');
|
|
89
154
|
fs.mkdirSync(backupDir, { recursive: true });
|
|
@@ -94,8 +159,8 @@ export default class CatalogBackup extends BaseCommand {
|
|
|
94
159
|
const totalThreads = backupFaces.reduce((n, f) => n + f.threads.length, 0);
|
|
95
160
|
if (!json) {
|
|
96
161
|
this.log(`\nBackup saved: ${filePath}`);
|
|
97
|
-
this.log(` ${backupFaces.length} face(s), ${totalDocs} document(s), ${totalThreads} thread(s)`);
|
|
162
|
+
this.log(` ${backupFaces.length} face(s), ${totalDocs} document(s), ${totalThreads} thread(s), ${backupTeams.length} team(s)`);
|
|
98
163
|
}
|
|
99
|
-
return { path: filePath, faces: backupFaces.length, documents: totalDocs, threads: totalThreads };
|
|
164
|
+
return { path: filePath, faces: backupFaces.length, documents: totalDocs, threads: totalThreads, teams: backupTeams.length };
|
|
100
165
|
}
|
|
101
166
|
}
|
|
@@ -130,14 +130,14 @@ export default class CatalogDoctor extends BaseCommand {
|
|
|
130
130
|
// Fix mode: rebuild missing and stale entries
|
|
131
131
|
let fixed = 0;
|
|
132
132
|
for (const face of [...missing, ...stale]) {
|
|
133
|
-
catalog.writeFace(face);
|
|
133
|
+
catalog.writeFace(face, face.description ?? undefined);
|
|
134
134
|
fixed++;
|
|
135
135
|
}
|
|
136
136
|
// Also refresh faces that exist but might have stale attributes
|
|
137
137
|
for (const [alias, remote] of remoteByAlias) {
|
|
138
138
|
if (!missing.some((f) => f.alias === alias) && !stale.some((f) => f.alias === alias)) {
|
|
139
|
-
// Face exists locally and name matches — still refresh attributes
|
|
140
|
-
catalog.writeFace(remote);
|
|
139
|
+
// Face exists locally and name matches — still refresh attributes and description
|
|
140
|
+
catalog.writeFace(remote, remote.description ?? undefined);
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
// Remove orphans
|
|
@@ -175,7 +175,13 @@ export default class CatalogDoctor extends BaseCommand {
|
|
|
175
175
|
text = resp.choices?.[0]?.message?.content;
|
|
176
176
|
}
|
|
177
177
|
if (text) {
|
|
178
|
-
|
|
178
|
+
const desc = text.trim();
|
|
179
|
+
catalog.writeFace(remote, desc);
|
|
180
|
+
// Also sync description to server
|
|
181
|
+
try {
|
|
182
|
+
await client.patch(`/v1/faces/${alias}`, { body: { description: desc } });
|
|
183
|
+
}
|
|
184
|
+
catch { /* non-fatal */ }
|
|
179
185
|
generated++;
|
|
180
186
|
this.log(` ${alias}: done`);
|
|
181
187
|
}
|
|
@@ -6,7 +6,7 @@ import { BaseCommand } from '../../base.js';
|
|
|
6
6
|
import { FacesAPIError } from '../../client.js';
|
|
7
7
|
import { CatalogService } from '../../catalog.js';
|
|
8
8
|
export default class CatalogRestore extends BaseCommand {
|
|
9
|
-
static description = 'Restore faces and source material from a backup snapshot';
|
|
9
|
+
static description = 'Restore faces, teams, and source material from a backup snapshot';
|
|
10
10
|
static flags = {
|
|
11
11
|
...BaseCommand.baseFlags,
|
|
12
12
|
compile: Flags.boolean({ description: 'Run compile:all after restoring', default: false }),
|
|
@@ -22,23 +22,23 @@ export default class CatalogRestore extends BaseCommand {
|
|
|
22
22
|
if (!fs.existsSync(filePath))
|
|
23
23
|
this.error(`Backup file not found: ${filePath}`);
|
|
24
24
|
const backup = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
25
|
-
if (backup.version !== 1)
|
|
25
|
+
if (backup.version !== 1 && backup.version !== 2)
|
|
26
26
|
this.error(`Unsupported backup version: ${backup.version}`);
|
|
27
27
|
if (!json) {
|
|
28
28
|
this.log(`Restoring from: ${filePath}`);
|
|
29
29
|
this.log(` Created: ${backup.created_at}`);
|
|
30
|
-
this.log(` Faces: ${backup.faces.length}\n`);
|
|
30
|
+
this.log(` Faces: ${backup.faces.length}, Teams: ${backup.teams?.length ?? 0}\n`);
|
|
31
31
|
}
|
|
32
32
|
const catalog = new CatalogService();
|
|
33
33
|
let facesCreated = 0;
|
|
34
34
|
let facesSkipped = 0;
|
|
35
35
|
let docsUploaded = 0;
|
|
36
36
|
let threadsUploaded = 0;
|
|
37
|
+
let teamsCreated = 0;
|
|
37
38
|
for (const face of backup.faces) {
|
|
38
39
|
if (!json)
|
|
39
40
|
process.stderr.write(`${face.alias}: `);
|
|
40
|
-
|
|
41
|
-
const created = await this.createFace(client, face, json);
|
|
41
|
+
const created = await this.createFace(client, face);
|
|
42
42
|
if (!created) {
|
|
43
43
|
facesSkipped++;
|
|
44
44
|
if (!json)
|
|
@@ -46,9 +46,16 @@ export default class CatalogRestore extends BaseCommand {
|
|
|
46
46
|
continue;
|
|
47
47
|
}
|
|
48
48
|
facesCreated++;
|
|
49
|
+
// Post tags
|
|
50
|
+
if (face.tags && face.tags.length > 0) {
|
|
51
|
+
try {
|
|
52
|
+
await client.post(`/v1/iam/${face.alias}/tags`, { body: { tags: face.tags } });
|
|
53
|
+
}
|
|
54
|
+
catch { /* non-fatal */ }
|
|
55
|
+
}
|
|
49
56
|
// Write to local catalog
|
|
50
57
|
try {
|
|
51
|
-
catalog.writeFace({ alias: face.alias, name: face.name, basic_facts: face.basic_facts });
|
|
58
|
+
catalog.writeFace({ alias: face.alias, name: face.name, basic_facts: face.basic_facts }, face.description ?? undefined);
|
|
52
59
|
}
|
|
53
60
|
catch { /* non-fatal */ }
|
|
54
61
|
// Upload documents
|
|
@@ -68,7 +75,7 @@ export default class CatalogRestore extends BaseCommand {
|
|
|
68
75
|
process.stderr.write(`\n warn: doc upload failed: ${msg}\n `);
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
|
-
// Upload threads
|
|
78
|
+
// Upload threads
|
|
72
79
|
for (const thread of face.threads) {
|
|
73
80
|
try {
|
|
74
81
|
const createPayload = { alias: face.alias };
|
|
@@ -76,7 +83,6 @@ export default class CatalogRestore extends BaseCommand {
|
|
|
76
83
|
createPayload.label = thread.label;
|
|
77
84
|
const created = await client.post('/v1/compile/threads', { body: createPayload });
|
|
78
85
|
const threadId = created.thread_id;
|
|
79
|
-
// Overwrite the auto-generated messages with the backup messages
|
|
80
86
|
if (thread.messages.length > 0) {
|
|
81
87
|
await client.patch(`/v1/compile/threads/${threadId}/messages`, {
|
|
82
88
|
body: { messages: thread.messages },
|
|
@@ -93,21 +99,69 @@ export default class CatalogRestore extends BaseCommand {
|
|
|
93
99
|
if (!json)
|
|
94
100
|
process.stderr.write(`${face.documents.length} doc(s), ${face.threads.length} thread(s)\n`);
|
|
95
101
|
}
|
|
102
|
+
// Restore teams (v2+)
|
|
103
|
+
if (backup.teams && backup.teams.length > 0) {
|
|
104
|
+
if (!json)
|
|
105
|
+
this.log(`\nRestoring ${backup.teams.length} team(s)...`);
|
|
106
|
+
for (const team of backup.teams) {
|
|
107
|
+
if (!json)
|
|
108
|
+
process.stderr.write(` ${team.name}: `);
|
|
109
|
+
try {
|
|
110
|
+
const payload = { name: team.name };
|
|
111
|
+
if (team.description)
|
|
112
|
+
payload.description = team.description;
|
|
113
|
+
if (team.protocol)
|
|
114
|
+
payload.protocol = team.protocol;
|
|
115
|
+
const created = await client.post('/v1/teams', { body: payload });
|
|
116
|
+
const teamId = created.id;
|
|
117
|
+
// Add tags
|
|
118
|
+
if (team.tags.length > 0) {
|
|
119
|
+
try {
|
|
120
|
+
await client.post(`/v1/teams/${teamId}/tags`, { body: { tags: team.tags } });
|
|
121
|
+
}
|
|
122
|
+
catch { /* non-fatal */ }
|
|
123
|
+
}
|
|
124
|
+
// Add members (resolve aliases to iam_ids)
|
|
125
|
+
for (const alias of team.members) {
|
|
126
|
+
try {
|
|
127
|
+
const face = await client.get(`/v1/faces/${alias}`);
|
|
128
|
+
const iamId = (face.uid ?? face.id ?? face.iam_id);
|
|
129
|
+
if (iamId) {
|
|
130
|
+
await client.post(`/v1/teams/${teamId}/members`, { body: { iam_id: iamId } });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch { /* member may not exist yet */ }
|
|
134
|
+
}
|
|
135
|
+
teamsCreated++;
|
|
136
|
+
if (!json)
|
|
137
|
+
process.stderr.write(`${team.members.length} member(s)\n`);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const msg = err instanceof FacesAPIError ? `${err.statusCode}: ${err.message}` : String(err);
|
|
141
|
+
if (!json)
|
|
142
|
+
process.stderr.write(`failed: ${msg}\n`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
96
146
|
if (!json) {
|
|
97
147
|
this.log(`\nRestore complete:`);
|
|
98
148
|
this.log(` Faces created: ${facesCreated}, skipped: ${facesSkipped}`);
|
|
99
149
|
this.log(` Documents uploaded: ${docsUploaded}`);
|
|
100
150
|
this.log(` Threads uploaded: ${threadsUploaded}`);
|
|
151
|
+
if (teamsCreated > 0)
|
|
152
|
+
this.log(` Teams created: ${teamsCreated}`);
|
|
101
153
|
}
|
|
102
154
|
if (flags.compile) {
|
|
103
155
|
if (!json)
|
|
104
156
|
this.log('\nRunning compile:all...\n');
|
|
105
157
|
await this.config.runCommand('compile:all', json ? ['--json'] : []);
|
|
106
158
|
}
|
|
107
|
-
return { faces_created: facesCreated, faces_skipped: facesSkipped, documents: docsUploaded, threads: threadsUploaded };
|
|
159
|
+
return { faces_created: facesCreated, faces_skipped: facesSkipped, documents: docsUploaded, threads: threadsUploaded, teams_created: teamsCreated };
|
|
108
160
|
}
|
|
109
|
-
async createFace(client, face
|
|
161
|
+
async createFace(client, face) {
|
|
110
162
|
const payload = { name: face.name, alias: face.alias };
|
|
163
|
+
if (face.description)
|
|
164
|
+
payload.description = face.description;
|
|
111
165
|
if (face.formula) {
|
|
112
166
|
payload.formula = face.formula;
|
|
113
167
|
if (face.default_model)
|
|
@@ -127,7 +181,6 @@ export default class CatalogRestore extends BaseCommand {
|
|
|
127
181
|
}
|
|
128
182
|
catch (err) {
|
|
129
183
|
if (err instanceof FacesAPIError && err.statusCode === 400) {
|
|
130
|
-
// likely "already have a face with alias"
|
|
131
184
|
return false;
|
|
132
185
|
}
|
|
133
186
|
throw err;
|
|
@@ -5,6 +5,7 @@ export default class FaceCreate extends BaseCommand {
|
|
|
5
5
|
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
6
|
alias: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
7
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
'default-model': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
formula: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
attr: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -9,7 +9,8 @@ export default class FaceCreate extends BaseCommand {
|
|
|
9
9
|
...BaseCommand.baseFlags,
|
|
10
10
|
name: Flags.string({ description: 'Display name', required: true }),
|
|
11
11
|
alias: Flags.string({ description: 'Unique alias slug', required: true }),
|
|
12
|
-
description: Flags.string({ description: '
|
|
12
|
+
description: Flags.string({ description: 'Plain-text description / bio (max 1500 chars)' }),
|
|
13
|
+
tag: Flags.string({ description: 'Tag label (repeatable, lowercase)', multiple: true }),
|
|
13
14
|
'default-model': Flags.string({ description: 'Default LLM for chat (e.g. gpt-4o-mini, claude-sonnet-4-6)' }),
|
|
14
15
|
formula: Flags.string({ description: 'Boolean formula over owned concrete face aliases (e.g. "a | b", "(a | b) - c"). Creates a composite face.' }),
|
|
15
16
|
attr: Flags.string({ description: 'Attribute KEY=VALUE (repeatable)', multiple: true }),
|
|
@@ -23,6 +24,8 @@ export default class FaceCreate extends BaseCommand {
|
|
|
23
24
|
this.error('--formula cannot be combined with --attr or --tool. Composite faces do not have compiled knowledge.');
|
|
24
25
|
}
|
|
25
26
|
const payload = { name: flags.name, alias: flags.alias };
|
|
27
|
+
if (flags.description)
|
|
28
|
+
payload.description = flags.description;
|
|
26
29
|
if (flags.formula) {
|
|
27
30
|
payload.formula = flags.formula;
|
|
28
31
|
if (flags['default-model'])
|
|
@@ -60,6 +63,16 @@ export default class FaceCreate extends BaseCommand {
|
|
|
60
63
|
if (face.basic_facts && typeof face.basic_facts === 'object') {
|
|
61
64
|
face.basic_facts = flattenBasicFacts(face.basic_facts);
|
|
62
65
|
}
|
|
66
|
+
// Post tags if provided
|
|
67
|
+
if (flags.tag && flags.tag.length > 0) {
|
|
68
|
+
try {
|
|
69
|
+
await client.post(`/v1/iam/${flags.alias}/tags`, { body: { tags: flags.tag } });
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (err instanceof FacesAPIError)
|
|
73
|
+
this.warn(`Tags failed (${err.statusCode}): ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
63
76
|
try {
|
|
64
77
|
const catalog = new CatalogService();
|
|
65
78
|
catalog.writeFace(face, flags.description);
|
|
@@ -2,6 +2,7 @@ import { BaseCommand } from '../../base.js';
|
|
|
2
2
|
export default class FaceList extends BaseCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static flags: {
|
|
5
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
5
6
|
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
7
|
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
8
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
1
2
|
import { BaseCommand } from '../../base.js';
|
|
2
3
|
import { FacesAPIError } from '../../client.js';
|
|
3
4
|
import { flattenBasicFacts } from '../../utils.js';
|
|
@@ -5,13 +6,22 @@ export default class FaceList extends BaseCommand {
|
|
|
5
6
|
static description = 'List all owned faces';
|
|
6
7
|
static flags = {
|
|
7
8
|
...BaseCommand.baseFlags,
|
|
9
|
+
tag: Flags.string({ description: 'Filter by tag (repeatable, AND logic)', multiple: true }),
|
|
8
10
|
};
|
|
9
11
|
async run() {
|
|
10
12
|
const { flags } = await this.parse(FaceList);
|
|
11
13
|
const client = this.makeClient(flags);
|
|
14
|
+
const params = {};
|
|
15
|
+
// Tags need to be passed as repeated query params: ?tag=a&tag=b
|
|
16
|
+
// Build the query string manually since URLSearchParams dedupes keys
|
|
17
|
+
let tagQuery = '';
|
|
18
|
+
if (flags.tag && flags.tag.length > 0) {
|
|
19
|
+
tagQuery = flags.tag.map(t => `tag=${encodeURIComponent(t)}`).join('&');
|
|
20
|
+
}
|
|
12
21
|
let data;
|
|
13
22
|
try {
|
|
14
|
-
|
|
23
|
+
const path = tagQuery ? `/v1/faces?${tagQuery}` : '/v1/faces';
|
|
24
|
+
data = await client.get(path);
|
|
15
25
|
}
|
|
16
26
|
catch (err) {
|
|
17
27
|
if (err instanceof FacesAPIError)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base.js';
|
|
2
|
+
export default class FaceTagAdd extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string[], 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
|
+
static args: {
|
|
11
|
+
alias: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<unknown>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../../base.js';
|
|
3
|
+
import { FacesAPIError } from '../../../client.js';
|
|
4
|
+
export default class FaceTagAdd extends BaseCommand {
|
|
5
|
+
static description = 'Add tags to a face (appends, idempotent)';
|
|
6
|
+
static flags = {
|
|
7
|
+
...BaseCommand.baseFlags,
|
|
8
|
+
tag: Flags.string({ description: 'Tag to add (repeatable)', multiple: true, required: true }),
|
|
9
|
+
};
|
|
10
|
+
static args = {
|
|
11
|
+
alias: Args.string({ description: 'Face alias', required: true }),
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
const { args, flags } = await this.parse(FaceTagAdd);
|
|
15
|
+
const client = this.makeClient(flags);
|
|
16
|
+
let data;
|
|
17
|
+
try {
|
|
18
|
+
data = await client.post(`/v1/iam/${args.alias}/tags`, { body: { tags: flags.tag } });
|
|
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
|
+
this.printHuman(data);
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base.js';
|
|
2
|
+
export default class FaceTagList 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,35 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../../base.js';
|
|
3
|
+
import { FacesAPIError } from '../../../client.js';
|
|
4
|
+
export default class FaceTagList extends BaseCommand {
|
|
5
|
+
static description = 'List tags on a face';
|
|
6
|
+
static flags = {
|
|
7
|
+
...BaseCommand.baseFlags,
|
|
8
|
+
};
|
|
9
|
+
static args = {
|
|
10
|
+
alias: Args.string({ description: 'Face alias', required: true }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { args, flags } = await this.parse(FaceTagList);
|
|
14
|
+
const client = this.makeClient(flags);
|
|
15
|
+
let data;
|
|
16
|
+
try {
|
|
17
|
+
data = await client.get(`/v1/iam/${args.alias}/tags`);
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err instanceof FacesAPIError)
|
|
21
|
+
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
if (!this.jsonEnabled()) {
|
|
25
|
+
const tags = data.tags ?? [];
|
|
26
|
+
if (tags.length === 0) {
|
|
27
|
+
this.log('(no tags)');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
this.log(tags.join(', '));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
}
|