@zeyos/cli 0.2.0 → 0.4.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.
- package/README.md +15 -1
- package/bin/zeyos.mjs +17 -1
- package/commands/count.mjs +1 -1
- package/commands/create.mjs +1 -1
- package/commands/delete.mjs +1 -1
- package/commands/get.mjs +1 -1
- package/commands/list.mjs +1 -1
- package/commands/login.mjs +33 -8
- package/commands/logout.mjs +12 -7
- package/commands/okf.mjs +192 -0
- package/commands/profile.mjs +211 -0
- package/commands/update.mjs +1 -1
- package/commands/whoami.mjs +9 -2
- package/config/actionstep.json +21 -0
- package/config/customfield.json +17 -0
- package/config/message.json +20 -0
- package/config/task.json +4 -2
- package/config/ticket.json +2 -1
- package/lib/client.mjs +19 -10
- package/lib/command.mjs +2 -2
- package/lib/config.mjs +280 -45
- package/lib/flags.mjs +1 -1
- package/lib/resources.mjs +26 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -72,6 +72,20 @@ For larger or reusable filters, put the JSON in a file:
|
|
|
72
72
|
zeyos list tickets --filter-file ./filters/open-tickets.json --json
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
Inspect dynamic schema definitions:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
zeyos count customfields --json
|
|
79
|
+
zeyos list customfields --fields ID,name,identifier,context,type --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Inspect actionsteps/time-entry evidence and ticket mail:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
zeyos list actionsteps --fields ID,name,status,date,duedate,effort,ticket,account --json
|
|
86
|
+
zeyos list messages --fields ID,date,mailbox,subject,sender_email,to_email,ticket,reference --filter '{"ticket":42}' --json
|
|
87
|
+
```
|
|
88
|
+
|
|
75
89
|
Create, update, and delete:
|
|
76
90
|
|
|
77
91
|
```bash
|
|
@@ -91,6 +105,6 @@ zeyos delete ticket 42
|
|
|
91
105
|
|
|
92
106
|
## Coverage Boundary
|
|
93
107
|
|
|
94
|
-
The CLI intentionally covers a curated registry instead of the full API surface.
|
|
108
|
+
The CLI intentionally covers a curated registry instead of the full API surface. It includes operational resources such as tickets, tasks, messages, and actionsteps; use `zeyos resources` to see the supported set.
|
|
95
109
|
|
|
96
110
|
When you need unsupported resources or low-level request control, switch to [`@zeyos/client`](../docs/02-javascript-client/01-getting-started.md) and follow the escalation guidance in [CLI Coverage and Escalation](../docs/04-agent-workflows/03-cli-coverage-and-escalation.md).
|
package/bin/zeyos.mjs
CHANGED
|
@@ -51,11 +51,14 @@ ${_c.bold('Commands:')}
|
|
|
51
51
|
${_c.cyan('describe')} <resource> Show a resource's fields, types and enums
|
|
52
52
|
${_c.cyan('doctor')} agent Check local CLI readiness for coding agents
|
|
53
53
|
${_c.cyan('skills')} <command> List / show / install ZeyOS agent skills
|
|
54
|
+
${_c.cyan('okf')} <command> List / show / check / export the OKF knowledge bundle
|
|
55
|
+
${_c.cyan('profile')} <command> Manage credential profiles / switch instances
|
|
54
56
|
|
|
55
57
|
${_c.bold('Global options:')}
|
|
56
58
|
--json Output as JSON
|
|
57
59
|
--yaml Output as YAML
|
|
58
60
|
--query Print the API route + JSON payload without sending it
|
|
61
|
+
--profile <name> Use a named credential profile for this command
|
|
59
62
|
--no-color Disable ANSI colors
|
|
60
63
|
-h, --help Show help for a command
|
|
61
64
|
-v, --version Print the CLI version and exit
|
|
@@ -82,6 +85,7 @@ const OPTIONS = {
|
|
|
82
85
|
'yaml': { type: 'boolean' },
|
|
83
86
|
'no-color': { type: 'boolean' },
|
|
84
87
|
'query': { type: 'boolean' },
|
|
88
|
+
'profile': { type: 'string' },
|
|
85
89
|
// login
|
|
86
90
|
'base-url': { type: 'string' },
|
|
87
91
|
'client-id': { type: 'string' },
|
|
@@ -117,6 +121,10 @@ const OPTIONS = {
|
|
|
117
121
|
'target': { type: 'string' },
|
|
118
122
|
'dir': { type: 'string' },
|
|
119
123
|
'no-logo': { type: 'boolean' },
|
|
124
|
+
// okf
|
|
125
|
+
'out': { type: 'string' },
|
|
126
|
+
// profile
|
|
127
|
+
'from-current': { type: 'boolean' },
|
|
120
128
|
};
|
|
121
129
|
|
|
122
130
|
// ── Command registry ──────────────────────────────────────────────────────────
|
|
@@ -142,6 +150,9 @@ const COMMANDS = {
|
|
|
142
150
|
doctor: '../commands/doctor.mjs',
|
|
143
151
|
skills: '../commands/skills.mjs',
|
|
144
152
|
skill: '../commands/skills.mjs',
|
|
153
|
+
okf: '../commands/okf.mjs',
|
|
154
|
+
profile: '../commands/profile.mjs',
|
|
155
|
+
profiles: '../commands/profile.mjs',
|
|
145
156
|
};
|
|
146
157
|
|
|
147
158
|
// ── Per-command flag allow-lists ────────────────────────────────────────────────
|
|
@@ -149,8 +160,10 @@ const COMMANDS = {
|
|
|
149
160
|
// immediately instead of being silently ignored. `create`/`update` are the
|
|
150
161
|
// exception: they accept arbitrary `--<field>` flags, marked with `null` below.
|
|
151
162
|
|
|
152
|
-
const ALWAYS_FLAGS = ['help', 'json', 'yaml', 'no-color'];
|
|
163
|
+
const ALWAYS_FLAGS = ['help', 'json', 'yaml', 'no-color', 'profile'];
|
|
153
164
|
const SKILLS_FLAGS = ['target', 'dir', 'global', 'local', 'force', 'yes', 'no-logo'];
|
|
165
|
+
const OKF_FLAGS = ['dir', 'out', 'force', 'no-logo'];
|
|
166
|
+
const PROFILE_FLAGS = ['base-url', 'client-id', 'secret', 'local', 'from-current'];
|
|
154
167
|
const DELETE_FLAGS = ['force', 'query'];
|
|
155
168
|
const GET_FLAGS = ['fields', 'extdata', 'tags', 'expand', 'all', 'query'];
|
|
156
169
|
|
|
@@ -174,6 +187,9 @@ const COMMAND_FLAGS = {
|
|
|
174
187
|
doctor: [],
|
|
175
188
|
skills: SKILLS_FLAGS,
|
|
176
189
|
skill: SKILLS_FLAGS,
|
|
190
|
+
okf: OKF_FLAGS,
|
|
191
|
+
profile: PROFILE_FLAGS,
|
|
192
|
+
profiles: PROFILE_FLAGS,
|
|
177
193
|
};
|
|
178
194
|
|
|
179
195
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
package/commands/count.mjs
CHANGED
|
@@ -51,7 +51,7 @@ export async function run(values, positional) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// ── Call API ───────────────────────────────────────────────────────────────
|
|
54
|
-
const clientState = buildCliClient();
|
|
54
|
+
const clientState = buildCliClient(values);
|
|
55
55
|
if (await maybeDryRun(clientState, res.list, body, values)) return;
|
|
56
56
|
|
|
57
57
|
const result = await callApi(clientState, res.list, body);
|
package/commands/create.mjs
CHANGED
|
@@ -48,7 +48,7 @@ export async function run(values, positional) {
|
|
|
48
48
|
// (optional) JSON body some callers pass positionally instead of via --data.
|
|
49
49
|
const data = buildRecordPayload(values, positional[1]);
|
|
50
50
|
|
|
51
|
-
const clientState = buildCliClient();
|
|
51
|
+
const clientState = buildCliClient(values);
|
|
52
52
|
|
|
53
53
|
// ── Call API ───────────────────────────────────────────────────────────────
|
|
54
54
|
if (await maybeDryRun(clientState, res.create, data, values)) return;
|
package/commands/delete.mjs
CHANGED
|
@@ -37,7 +37,7 @@ export async function run(values, positional) {
|
|
|
37
37
|
const res = requireResource(resourceName, 'zeyos delete <resource> <id>', 'delete', 'deletion');
|
|
38
38
|
requireRecordId(id, 'zeyos delete <resource> <id>');
|
|
39
39
|
|
|
40
|
-
const clientState = buildCliClient();
|
|
40
|
+
const clientState = buildCliClient(values);
|
|
41
41
|
|
|
42
42
|
// ── Dry run ────────────────────────────────────────────────────────────────
|
|
43
43
|
// Show the request without prompting or deleting anything.
|
package/commands/get.mjs
CHANGED
|
@@ -68,7 +68,7 @@ export async function run(values, positional) {
|
|
|
68
68
|
requireRecordId(id, 'zeyos get <resource> <id>');
|
|
69
69
|
|
|
70
70
|
const resName = canonicalName(resourceName);
|
|
71
|
-
const clientState = buildCliClient();
|
|
71
|
+
const clientState = buildCliClient(values);
|
|
72
72
|
|
|
73
73
|
// ── Build params ───────────────────────────────────────────────────────────
|
|
74
74
|
// GET endpoints use query parameters like ?extdata=1&tags=1 to include
|
package/commands/list.mjs
CHANGED
|
@@ -116,7 +116,7 @@ export async function run(values, positional) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// ── Call API ───────────────────────────────────────────────────────────────
|
|
119
|
-
const clientState = buildCliClient();
|
|
119
|
+
const clientState = buildCliClient(values);
|
|
120
120
|
if (await maybeDryRun(clientState, res.list, body, values)) return;
|
|
121
121
|
|
|
122
122
|
const fn = requireApiMethod(clientState, res.list);
|
package/commands/login.mjs
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { createInterface } from 'node:readline';
|
|
22
22
|
import { createZeyosClient, MemoryTokenStore } from '@zeyos/client';
|
|
23
|
-
import { saveConfig, loadConfig }
|
|
23
|
+
import { saveConfig, loadConfig, getProfile, upsertProfile, setActiveProfile } from '../lib/config.mjs';
|
|
24
24
|
import { waitForCallback, callbackUri, BrowserUnavailableError } from '../lib/login-server.mjs';
|
|
25
25
|
import { success, error, info, warn } from '../lib/output.mjs';
|
|
26
26
|
|
|
@@ -35,6 +35,7 @@ Options:
|
|
|
35
35
|
--base-url <url> ZeyOS platform URL (prompted if missing)
|
|
36
36
|
--client-id <id> OAuth client ID (prompted if missing)
|
|
37
37
|
--secret <secret> OAuth client secret (prompted if missing)
|
|
38
|
+
--profile <name> Store credentials/tokens in a named profile and activate it
|
|
38
39
|
--scope <scope> OAuth scope (default: all)
|
|
39
40
|
--port <port> Local callback server port (default: 9005)
|
|
40
41
|
--global Store credentials globally (~/.config/zeyos/credentials.json)
|
|
@@ -45,12 +46,21 @@ Options:
|
|
|
45
46
|
`;
|
|
46
47
|
|
|
47
48
|
export async function run(values) {
|
|
49
|
+
const profileName = values.profile || null;
|
|
48
50
|
const scope = values.global ? 'global' : 'local';
|
|
49
51
|
const port = values.port ? Number(values.port) : DEFAULT_CALLBACK_PORT;
|
|
50
52
|
const redirectUri = callbackUri(port);
|
|
51
53
|
|
|
54
|
+
// Persist either into a named profile or the legacy local/global credential file.
|
|
55
|
+
const persist = (updates) => {
|
|
56
|
+
if (profileName) upsertProfile(profileName, updates);
|
|
57
|
+
else saveConfig(updates, scope);
|
|
58
|
+
};
|
|
59
|
+
|
|
52
60
|
// ── Resolve connection params ──────────────────────────────────────────────
|
|
53
|
-
const existing = values.clean
|
|
61
|
+
const existing = values.clean
|
|
62
|
+
? {}
|
|
63
|
+
: (profileName ? (getProfile(profileName) || {}) : loadConfig());
|
|
54
64
|
if (values.clean) values.force = true;
|
|
55
65
|
|
|
56
66
|
let baseUrl = values['base-url'] ?? existing.baseUrl;
|
|
@@ -79,12 +89,17 @@ export async function run(values) {
|
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
// Save connection params immediately so they are available on retries
|
|
82
|
-
|
|
92
|
+
persist({ baseUrl, clientId, clientSecret });
|
|
83
93
|
|
|
84
94
|
// ── Check if already authenticated ────────────────────────────────────────
|
|
85
95
|
if (existing.accessToken && !values.force) {
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
if (_tokenExpired(existing)) {
|
|
97
|
+
info('Stored access token has expired — re-authenticating…');
|
|
98
|
+
} else {
|
|
99
|
+
const where = profileName ? `profile "${profileName}"` : 'this scope';
|
|
100
|
+
warn(`Already logged in (${where}). Use --force to re-authenticate.`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
88
103
|
}
|
|
89
104
|
|
|
90
105
|
// ── Build a temporary client (no token yet) ────────────────────────────────
|
|
@@ -138,7 +153,7 @@ export async function run(values) {
|
|
|
138
153
|
info('Exchanging authorization code for tokens…');
|
|
139
154
|
const tokenSet = await client.oauth2.exchangeAuthorizationCode({ code, redirectUri });
|
|
140
155
|
|
|
141
|
-
|
|
156
|
+
persist({
|
|
142
157
|
baseUrl,
|
|
143
158
|
clientId,
|
|
144
159
|
clientSecret,
|
|
@@ -146,9 +161,12 @@ export async function run(values) {
|
|
|
146
161
|
refreshToken: tokenSet.refreshToken ?? undefined,
|
|
147
162
|
expiresAt: tokenSet.expiresAt ?? undefined,
|
|
148
163
|
refreshTokenExpiresAt: tokenSet.refreshTokenExpiresAt ?? undefined,
|
|
149
|
-
}
|
|
164
|
+
});
|
|
150
165
|
|
|
151
|
-
|
|
166
|
+
if (profileName) setActiveProfile(profileName);
|
|
167
|
+
success(profileName
|
|
168
|
+
? `Logged in — profile "${profileName}" is now active.`
|
|
169
|
+
: 'Logged in successfully.');
|
|
152
170
|
} catch (err) {
|
|
153
171
|
error(`Token exchange failed: ${err.message}`);
|
|
154
172
|
process.exit(1);
|
|
@@ -157,6 +175,13 @@ export async function run(values) {
|
|
|
157
175
|
|
|
158
176
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
159
177
|
|
|
178
|
+
/** True when stored creds carry an access token whose expiry is in the past. */
|
|
179
|
+
function _tokenExpired(creds) {
|
|
180
|
+
if (!creds?.accessToken || creds.expiresAt == null) return false;
|
|
181
|
+
const exp = Number(creds.expiresAt) > 2e10 ? Number(creds.expiresAt) / 1000 : Number(creds.expiresAt);
|
|
182
|
+
return Number.isFinite(exp) && exp < Math.floor(Date.now() / 1000);
|
|
183
|
+
}
|
|
184
|
+
|
|
160
185
|
/**
|
|
161
186
|
* Try the browser + callback server flow.
|
|
162
187
|
* On any failure, fall back to prompting the user for the code.
|
package/commands/logout.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createZeyosClient, MemoryTokenStore } from '@zeyos/client';
|
|
13
|
-
import {
|
|
13
|
+
import { loadConfigWithSource, clearTokens, clearTokensForSource } from '../lib/config.mjs';
|
|
14
14
|
import { success, warn, info } from '../lib/output.mjs';
|
|
15
15
|
|
|
16
16
|
export const USAGE = `\
|
|
@@ -19,13 +19,13 @@ Usage: zeyos logout [options]
|
|
|
19
19
|
Revoke the current session and clear stored tokens.
|
|
20
20
|
|
|
21
21
|
Options:
|
|
22
|
-
--
|
|
23
|
-
|
|
22
|
+
--profile <name> Log out of a specific profile
|
|
23
|
+
--global Target the legacy global credentials file
|
|
24
|
+
-h, --help Show this help
|
|
24
25
|
`;
|
|
25
26
|
|
|
26
27
|
export async function run(values) {
|
|
27
|
-
const
|
|
28
|
-
const config = loadConfig();
|
|
28
|
+
const { config, source } = loadConfigWithSource({ profile: values.profile });
|
|
29
29
|
|
|
30
30
|
if (!config.accessToken) {
|
|
31
31
|
warn('Not currently logged in.');
|
|
@@ -58,6 +58,11 @@ export async function run(values) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if (values.global) {
|
|
62
|
+
clearTokens('global');
|
|
63
|
+
} else {
|
|
64
|
+
clearTokensForSource(source);
|
|
65
|
+
}
|
|
66
|
+
const where = source?.kind === 'profile' ? `profile "${source.name}"` : (values.global ? 'global credentials' : 'local credentials');
|
|
67
|
+
success(`Logged out (${where}).`);
|
|
63
68
|
}
|
package/commands/okf.mjs
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zeyos okf <list|show|check|build|export>
|
|
3
|
+
*
|
|
4
|
+
* Work with the Open Knowledge Format (OKF v0.1) bundle that ships with
|
|
5
|
+
* @zeyos/client (under okf/): a directory of Markdown concept docs describing the
|
|
6
|
+
* ZeyOS data model (entities, schema, foreign keys, enums, indexes, operations)
|
|
7
|
+
* plus curated metrics, playbooks, and query concepts. Consumers — coding agents,
|
|
8
|
+
* viewers, search — read it; this command lists/shows/validates the shipped bundle
|
|
9
|
+
* and can synthesize or export one.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, existsSync, cpSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
|
|
17
|
+
import { loadOkfBundle, validateOkfFiles, buildOkf, OKF_VERSION } from '@zeyos/client';
|
|
18
|
+
import { outputMode, printJson, printYaml, printTable, colors, success, error, info, warn } from '../lib/output.mjs';
|
|
19
|
+
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
|
|
22
|
+
export const USAGE = `\
|
|
23
|
+
Usage: zeyos okf <command> [options]
|
|
24
|
+
|
|
25
|
+
Commands:
|
|
26
|
+
list List concepts in the OKF bundle (type, id, title)
|
|
27
|
+
show <concept> Print a concept doc (e.g. "tickets" or "entities/tickets")
|
|
28
|
+
check Validate the bundle for OKF v0.1 conformance
|
|
29
|
+
build [--out <dir>] Synthesize an OKF bundle from the client's schema
|
|
30
|
+
export [--out <dir>] Copy the shipped okf/ bundle into a directory
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--dir <path> Read from an explicit bundle directory (list/show/check)
|
|
34
|
+
--out <path> Write to this directory (build/export; default ./okf)
|
|
35
|
+
--force Overwrite an existing target (export)
|
|
36
|
+
--json Output as JSON
|
|
37
|
+
--yaml Output as YAML
|
|
38
|
+
-h, --help Show this help
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
zeyos okf list
|
|
42
|
+
zeyos okf show tickets
|
|
43
|
+
zeyos okf check
|
|
44
|
+
zeyos okf export --out ./vendor/okf
|
|
45
|
+
zeyos okf build --out ./okf-live`;
|
|
46
|
+
|
|
47
|
+
// Locate the okf/ bundle shipped inside the @zeyos/client package (mirrors the
|
|
48
|
+
// skills command's findAgentsDir).
|
|
49
|
+
function findOkfDir() {
|
|
50
|
+
let entry;
|
|
51
|
+
try {
|
|
52
|
+
entry = require.resolve('@zeyos/client');
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
let dir = path.dirname(entry);
|
|
57
|
+
for (let i = 0; i < 6; i++) {
|
|
58
|
+
const candidate = path.join(dir, 'okf');
|
|
59
|
+
if (existsSync(path.join(candidate, 'index.md'))) return candidate;
|
|
60
|
+
const parent = path.dirname(dir);
|
|
61
|
+
if (parent === dir) break;
|
|
62
|
+
dir = parent;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function expandHome(p) {
|
|
68
|
+
if (p === '~') return homedir();
|
|
69
|
+
if (p.startsWith('~/') || p.startsWith('~\\')) return path.join(homedir(), p.slice(2));
|
|
70
|
+
return p;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function displayPath(abs) {
|
|
74
|
+
const rel = path.relative(process.cwd(), abs);
|
|
75
|
+
if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) return rel;
|
|
76
|
+
const home = homedir();
|
|
77
|
+
if (abs === home || abs.startsWith(home + path.sep)) return '~' + abs.slice(home.length);
|
|
78
|
+
return abs;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resolveInputDir(values) {
|
|
82
|
+
if (values.dir) return path.resolve(expandHome(values.dir));
|
|
83
|
+
const found = findOkfDir();
|
|
84
|
+
if (!found) {
|
|
85
|
+
error('Could not locate the bundled OKF bundle (the @zeyos/client okf/ directory). Pass --dir <path>.');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
return found;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function conceptsToRows(concepts) {
|
|
92
|
+
return Object.entries(concepts)
|
|
93
|
+
.map(([id, { frontmatter }]) => ({ type: frontmatter.type || '(none)', concept: id, title: frontmatter.title || '' }))
|
|
94
|
+
.sort((a, b) => a.concept.localeCompare(b.concept));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function runList(values) {
|
|
98
|
+
const dir = resolveInputDir(values);
|
|
99
|
+
const { version, concepts } = await loadOkfBundle(dir);
|
|
100
|
+
const rows = conceptsToRows(concepts);
|
|
101
|
+
const mode = outputMode(values);
|
|
102
|
+
if (mode === 'json') return printJson({ version, concepts: rows });
|
|
103
|
+
if (mode === 'yaml') return printYaml({ version, concepts: rows });
|
|
104
|
+
printTable(rows, ['type', 'concept', 'title']);
|
|
105
|
+
info(`OKF v${version || OKF_VERSION} — ${rows.length} concepts in ${displayPath(dir)}. Show one with: zeyos okf show <concept>`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function runShow(values, name) {
|
|
109
|
+
const dir = resolveInputDir(values);
|
|
110
|
+
const candidates = [name, `${name}.md`, `entities/${name}.md`, path.join(dir, name), path.join(dir, `${name}.md`), path.join(dir, 'entities', `${name}.md`)];
|
|
111
|
+
for (const candidate of candidates) {
|
|
112
|
+
const abs = path.isAbsolute(candidate) ? candidate : path.join(dir, candidate);
|
|
113
|
+
if (existsSync(abs)) {
|
|
114
|
+
process.stdout.write(readFileSync(abs, 'utf8'));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
error(`Unknown concept "${name}". Run "zeyos okf list".`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function runCheck(values) {
|
|
123
|
+
const dir = resolveInputDir(values);
|
|
124
|
+
const { files } = await loadOkfBundle(dir);
|
|
125
|
+
const result = validateOkfFiles(files);
|
|
126
|
+
const mode = outputMode(values);
|
|
127
|
+
if (mode === 'json') { printJson(result); process.exit(result.valid ? 0 : 1); }
|
|
128
|
+
if (mode === 'yaml') { printYaml(result); process.exit(result.valid ? 0 : 1); }
|
|
129
|
+
if (result.valid) {
|
|
130
|
+
success(`OKF bundle is conformant: ${result.conceptCount} concepts, 0 errors (${displayPath(dir)}).`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const err of result.errors) error(`${err.path}: ${err.message}`);
|
|
134
|
+
error(`OKF bundle is NOT conformant: ${result.errors.length} error(s).`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function writeBundle(outDir, files) {
|
|
139
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
140
|
+
const abs = path.join(outDir, rel);
|
|
141
|
+
mkdirSync(path.dirname(abs), { recursive: true });
|
|
142
|
+
writeFileSync(abs, content, 'utf8');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function runBuild(values) {
|
|
147
|
+
const outDir = path.resolve(expandHome(values.out || 'okf'));
|
|
148
|
+
const files = buildOkf();
|
|
149
|
+
writeBundle(outDir, files);
|
|
150
|
+
const mode = outputMode(values);
|
|
151
|
+
const summary = { out: outDir, files: Object.keys(files).length };
|
|
152
|
+
if (mode === 'json') return printJson(summary);
|
|
153
|
+
if (mode === 'yaml') return printYaml(summary);
|
|
154
|
+
success(`Synthesized ${summary.files} OKF files → ${displayPath(outDir)}`);
|
|
155
|
+
info('This is the runtime projection from the client schema (structural only). The shipped okf/ bundle adds curated metrics, playbooks, and notes.');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function runExport(values) {
|
|
159
|
+
const dir = resolveInputDir(values);
|
|
160
|
+
const outDir = path.resolve(expandHome(values.out || 'okf'));
|
|
161
|
+
if (existsSync(outDir) && !values.force) {
|
|
162
|
+
if (path.resolve(dir) === outDir) {
|
|
163
|
+
warn(`Source and target are the same (${displayPath(outDir)}); nothing to do.`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
error(`Target ${displayPath(outDir)} already exists. Use --force to overwrite.`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
cpSync(dir, outDir, { recursive: true });
|
|
170
|
+
const mode = outputMode(values);
|
|
171
|
+
const summary = { from: dir, out: outDir };
|
|
172
|
+
if (mode === 'json') return printJson(summary);
|
|
173
|
+
if (mode === 'yaml') return printYaml(summary);
|
|
174
|
+
success(`Exported OKF bundle → ${displayPath(outDir)}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function run(values, positional = []) {
|
|
178
|
+
const sub = positional[0] || 'list';
|
|
179
|
+
const rest = positional.slice(1);
|
|
180
|
+
switch (sub) {
|
|
181
|
+
case 'list': return runList(values);
|
|
182
|
+
case 'show':
|
|
183
|
+
if (!rest[0]) { error('Usage: zeyos okf show <concept>'); process.exit(1); }
|
|
184
|
+
return runShow(values, rest[0]);
|
|
185
|
+
case 'check': return runCheck(values);
|
|
186
|
+
case 'build': return runBuild(values);
|
|
187
|
+
case 'export': return runExport(values);
|
|
188
|
+
default:
|
|
189
|
+
error(`Unknown okf command "${sub}".\n\n${USAGE}`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|