faces-cli 1.6.0 → 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/auth/register.d.ts +0 -1
- package/dist/commands/auth/register.js +1 -3
- package/dist/commands/billing/checkout.js +1 -1
- 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 +1693 -614
- package/package.json +1 -1
|
@@ -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 TeamTagAdd extends BaseCommand {
|
|
5
|
+
static description = 'Add tags to a team (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
|
+
team_id: Args.string({ description: 'Team ID', required: true }),
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
const { args, flags } = await this.parse(TeamTagAdd);
|
|
15
|
+
const client = this.makeClient(flags);
|
|
16
|
+
let data;
|
|
17
|
+
try {
|
|
18
|
+
data = await client.post(`/v1/teams/${args.team_id}/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 TeamTagList 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
|
+
team_id: 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 TeamTagList extends BaseCommand {
|
|
5
|
+
static description = 'List tags on a team';
|
|
6
|
+
static flags = {
|
|
7
|
+
...BaseCommand.baseFlags,
|
|
8
|
+
};
|
|
9
|
+
static args = {
|
|
10
|
+
team_id: Args.string({ description: 'Team ID', required: true }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { args, flags } = await this.parse(TeamTagList);
|
|
14
|
+
const client = this.makeClient(flags);
|
|
15
|
+
let data;
|
|
16
|
+
try {
|
|
17
|
+
data = await client.get(`/v1/teams/${args.team_id}/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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base.js';
|
|
2
|
+
export default class TeamTagRemove 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
|
+
team_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
+
tag: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<unknown>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../../base.js';
|
|
3
|
+
import { FacesAPIError } from '../../../client.js';
|
|
4
|
+
export default class TeamTagRemove extends BaseCommand {
|
|
5
|
+
static description = 'Remove a tag from a team';
|
|
6
|
+
static flags = {
|
|
7
|
+
...BaseCommand.baseFlags,
|
|
8
|
+
};
|
|
9
|
+
static args = {
|
|
10
|
+
team_id: Args.string({ description: 'Team ID', required: true }),
|
|
11
|
+
tag: Args.string({ description: 'Tag to remove', required: true }),
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
const { args, flags } = await this.parse(TeamTagRemove);
|
|
15
|
+
const client = this.makeClient(flags);
|
|
16
|
+
let data;
|
|
17
|
+
try {
|
|
18
|
+
data = await client.delete(`/v1/teams/${args.team_id}/tags/${encodeURIComponent(args.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.log(`Removed tag '${args.tag}' from team.`);
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base.js';
|
|
2
|
+
export default class TeamTagSet 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
|
+
team_id: 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 TeamTagSet extends BaseCommand {
|
|
5
|
+
static description = 'Replace all tags on a team';
|
|
6
|
+
static flags = {
|
|
7
|
+
...BaseCommand.baseFlags,
|
|
8
|
+
tag: Flags.string({ description: 'Tag to set (repeatable, replaces all)', multiple: true, required: true }),
|
|
9
|
+
};
|
|
10
|
+
static args = {
|
|
11
|
+
team_id: Args.string({ description: 'Team ID', required: true }),
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
const { args, flags } = await this.parse(TeamTagSet);
|
|
15
|
+
const client = this.makeClient(flags);
|
|
16
|
+
let data;
|
|
17
|
+
try {
|
|
18
|
+
data = await client.put(`/v1/teams/${args.team_id}/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,17 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class TeamUpdate extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
protocol: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'protocol-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
static args: {
|
|
14
|
+
team_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<unknown>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { BaseCommand } from '../../base.js';
|
|
4
|
+
import { FacesAPIError } from '../../client.js';
|
|
5
|
+
export default class TeamUpdate extends BaseCommand {
|
|
6
|
+
static description = 'Update a team';
|
|
7
|
+
static flags = {
|
|
8
|
+
...BaseCommand.baseFlags,
|
|
9
|
+
name: Flags.string({ description: 'New team name' }),
|
|
10
|
+
description: Flags.string({ description: 'New description (max 1500 chars)' }),
|
|
11
|
+
protocol: Flags.string({ description: 'New protocol content' }),
|
|
12
|
+
'protocol-file': Flags.string({ description: 'Read protocol from file' }),
|
|
13
|
+
};
|
|
14
|
+
static args = {
|
|
15
|
+
team_id: Args.string({ description: 'Team ID', required: true }),
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
const { args, flags } = await this.parse(TeamUpdate);
|
|
19
|
+
const client = this.makeClient(flags);
|
|
20
|
+
let protocol = flags.protocol;
|
|
21
|
+
if (flags['protocol-file']) {
|
|
22
|
+
if (!fs.existsSync(flags['protocol-file']))
|
|
23
|
+
this.error(`File not found: ${flags['protocol-file']}`);
|
|
24
|
+
protocol = fs.readFileSync(flags['protocol-file'], 'utf8');
|
|
25
|
+
}
|
|
26
|
+
const payload = {};
|
|
27
|
+
if (flags.name)
|
|
28
|
+
payload.name = flags.name;
|
|
29
|
+
if (flags.description !== undefined)
|
|
30
|
+
payload.description = flags.description;
|
|
31
|
+
if (protocol !== undefined)
|
|
32
|
+
payload.protocol = protocol;
|
|
33
|
+
if (Object.keys(payload).length === 0)
|
|
34
|
+
this.error('Provide at least one field to update.');
|
|
35
|
+
let data;
|
|
36
|
+
try {
|
|
37
|
+
data = await client.patch(`/v1/teams/${args.team_id}`, { body: payload });
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (err instanceof FacesAPIError)
|
|
41
|
+
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
if (!this.jsonEnabled())
|
|
45
|
+
this.printHuman(data);
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const TEAMS_DIR: string;
|
|
2
|
+
export interface TeamFrontmatter {
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
tags?: string[];
|
|
6
|
+
members?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare class TeamCatalogService {
|
|
9
|
+
writeTeam(name: string, fm: TeamFrontmatter, protocol?: string | null): void;
|
|
10
|
+
readTeam(name: string): {
|
|
11
|
+
frontmatter: TeamFrontmatter;
|
|
12
|
+
body: string;
|
|
13
|
+
} | null;
|
|
14
|
+
listTeams(): string[];
|
|
15
|
+
deleteTeam(name: string): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export const TEAMS_DIR = path.join(os.homedir(), '.faces', 'teams');
|
|
5
|
+
function quoteYaml(value) {
|
|
6
|
+
if (/[:#\[\]{}&*!|>'"%@`\n]/.test(value) || value !== value.trim() || value === '') {
|
|
7
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function serializeFrontmatter(fm) {
|
|
12
|
+
const lines = ['---'];
|
|
13
|
+
lines.push(`name: ${quoteYaml(fm.name)}`);
|
|
14
|
+
if (fm.description)
|
|
15
|
+
lines.push(`description: ${quoteYaml(fm.description)}`);
|
|
16
|
+
if (fm.tags && fm.tags.length > 0) {
|
|
17
|
+
lines.push(`tags: [${fm.tags.join(', ')}]`);
|
|
18
|
+
}
|
|
19
|
+
if (fm.members && fm.members.length > 0) {
|
|
20
|
+
lines.push(`members: [${fm.members.join(', ')}]`);
|
|
21
|
+
}
|
|
22
|
+
lines.push('---');
|
|
23
|
+
return lines.join('\n');
|
|
24
|
+
}
|
|
25
|
+
function parseFrontmatter(content) {
|
|
26
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
27
|
+
if (!match)
|
|
28
|
+
return { frontmatter: null, body: content };
|
|
29
|
+
const yamlBlock = match[1];
|
|
30
|
+
const body = match[2];
|
|
31
|
+
const fm = {};
|
|
32
|
+
for (const line of yamlBlock.split('\n')) {
|
|
33
|
+
const colonIdx = line.indexOf(':');
|
|
34
|
+
if (colonIdx < 0)
|
|
35
|
+
continue;
|
|
36
|
+
const key = line.slice(0, colonIdx).trim();
|
|
37
|
+
const val = line.slice(colonIdx + 1).trim();
|
|
38
|
+
if (key === 'name') {
|
|
39
|
+
fm.name = val.replace(/^"(.*)"$/, '$1');
|
|
40
|
+
}
|
|
41
|
+
else if (key === 'description') {
|
|
42
|
+
fm.description = val.replace(/^"(.*)"$/, '$1');
|
|
43
|
+
}
|
|
44
|
+
else if (key === 'tags') {
|
|
45
|
+
const inner = val.replace(/^\[/, '').replace(/\]$/, '');
|
|
46
|
+
fm.tags = inner ? inner.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
47
|
+
}
|
|
48
|
+
else if (key === 'members') {
|
|
49
|
+
const inner = val.replace(/^\[/, '').replace(/\]$/, '');
|
|
50
|
+
fm.members = inner ? inner.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!fm.name)
|
|
54
|
+
return { frontmatter: null, body: content };
|
|
55
|
+
return { frontmatter: fm, body };
|
|
56
|
+
}
|
|
57
|
+
export class TeamCatalogService {
|
|
58
|
+
writeTeam(name, fm, protocol) {
|
|
59
|
+
try {
|
|
60
|
+
const dir = path.join(TEAMS_DIR, name);
|
|
61
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
62
|
+
const filePath = path.join(dir, 'TEAM.md');
|
|
63
|
+
const body = protocol ?? '';
|
|
64
|
+
const content = serializeFrontmatter(fm) + '\n' + body;
|
|
65
|
+
fs.writeFileSync(filePath, content);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69
|
+
process.stderr.write(`warn: team catalog write failed: ${msg}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
readTeam(name) {
|
|
73
|
+
const filePath = path.join(TEAMS_DIR, name, 'TEAM.md');
|
|
74
|
+
if (!fs.existsSync(filePath))
|
|
75
|
+
return null;
|
|
76
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
77
|
+
const parsed = parseFrontmatter(content);
|
|
78
|
+
if (!parsed.frontmatter)
|
|
79
|
+
return null;
|
|
80
|
+
return { frontmatter: parsed.frontmatter, body: parsed.body };
|
|
81
|
+
}
|
|
82
|
+
listTeams() {
|
|
83
|
+
if (!fs.existsSync(TEAMS_DIR))
|
|
84
|
+
return [];
|
|
85
|
+
return fs.readdirSync(TEAMS_DIR, { withFileTypes: true })
|
|
86
|
+
.filter(d => d.isDirectory() && fs.existsSync(path.join(TEAMS_DIR, d.name, 'TEAM.md')))
|
|
87
|
+
.map(d => d.name);
|
|
88
|
+
}
|
|
89
|
+
deleteTeam(name) {
|
|
90
|
+
try {
|
|
91
|
+
const dir = path.join(TEAMS_DIR, name);
|
|
92
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
96
|
+
process.stderr.write(`warn: team catalog delete failed: ${msg}\n`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|