@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.
@@ -0,0 +1,211 @@
1
+ /**
2
+ * zeyos profile <list|current|use|add|remove>
3
+ *
4
+ * Manage named credential profiles (e.g. dev, prod, client-x) and switch between
5
+ * ZeyOS instances without re-running login each time.
6
+ *
7
+ * list Show all profiles (active marked with *)
8
+ * current Show the profile that resolves right now, and why
9
+ * use <name> [--local] Make <name> active globally, or pin it to this project
10
+ * add <name> [opts] Create/update a profile's connection params
11
+ * remove <name> Delete a profile
12
+ *
13
+ * `add` options: --base-url, --client-id, --secret, or --from-current to snapshot
14
+ * the credentials currently in effect (including tokens) into the new profile.
15
+ */
16
+
17
+ import {
18
+ listProfiles, getProfile, upsertProfile, removeProfile,
19
+ setActiveProfile, writeLocalPin, readLocalPin,
20
+ resolveProfileSelection, loadConfigWithSource, profilesConfigPath
21
+ } from '../lib/config.mjs';
22
+ import { outputMode, printJson, printYaml, printTable, success, error, info, warn } from '../lib/output.mjs';
23
+
24
+ export const USAGE = `\
25
+ Usage: zeyos profile <command> [options]
26
+
27
+ Manage named credential profiles and switch between ZeyOS instances.
28
+
29
+ Commands:
30
+ list List all profiles (active marked with *)
31
+ current Show which profile is in effect, and why
32
+ use <name> Make <name> the active profile (global)
33
+ use <name> --local Pin <name> to the current project (.zeyos/profile)
34
+ add <name> [options] Create or update a profile
35
+ remove <name> Delete a profile
36
+
37
+ Add options:
38
+ --base-url <url> ZeyOS platform URL for the profile
39
+ --client-id <id> OAuth client ID
40
+ --secret <secret> OAuth client secret
41
+ --from-current Snapshot the credentials currently in effect (incl. tokens)
42
+
43
+ Global options:
44
+ --json | --yaml Machine-readable output (list / current)
45
+ -h, --help Show this help
46
+
47
+ Examples:
48
+ zeyos profile add dev --base-url https://zeyos.cms-it.de/dev
49
+ zeyos profile add prod --from-current
50
+ zeyos profile use prod
51
+ zeyos profile use dev --local # only inside this project
52
+ zeyos whoami --profile dev # one-off override on any command
53
+ `;
54
+
55
+ export async function run(values, positional) {
56
+ const sub = positional[0] || 'list';
57
+ switch (sub) {
58
+ case 'list': return cmdList(values);
59
+ case 'current': return cmdCurrent(values);
60
+ case 'use': return cmdUse(values, positional[1]);
61
+ case 'add': return cmdAdd(values, positional[1]);
62
+ case 'remove':
63
+ case 'rm':
64
+ case 'delete': return cmdRemove(values, positional[1]);
65
+ default:
66
+ error(`Unknown profile command: "${sub}".`);
67
+ process.stderr.write(`\n${USAGE}`);
68
+ process.exit(1);
69
+ }
70
+ }
71
+
72
+ // ── list ───────────────────────────────────────────────────────────────────────
73
+
74
+ function cmdList(values) {
75
+ const { active, profiles } = listProfiles();
76
+ const names = Object.keys(profiles);
77
+ const mode = outputMode(values);
78
+
79
+ if (mode === 'json') return printJson({ active, profiles });
80
+ if (mode === 'yaml') return printYaml({ active, profiles });
81
+
82
+ if (names.length === 0) {
83
+ info('No profiles defined yet.');
84
+ console.error(`Create one with: zeyos profile add <name> --base-url <url>`);
85
+ console.error(` or: zeyos login --profile <name>`);
86
+ return;
87
+ }
88
+
89
+ const rows = names.map((name) => ({
90
+ name: `${name === active ? '*' : ' '} ${name}`,
91
+ baseUrl: profiles[name].baseUrl || '(no URL)',
92
+ token: tokenStatus(profiles[name])
93
+ }));
94
+ printTable(rows, ['name', 'baseUrl', 'token'], { name: 'PROFILE', baseUrl: 'BASE URL', token: 'TOKEN' });
95
+ console.error(`\nProfiles file: ${profilesConfigPath()}`);
96
+ }
97
+
98
+ // ── current ────────────────────────────────────────────────────────────────────
99
+
100
+ function cmdCurrent(values) {
101
+ const selection = resolveProfileSelection({ profileFlag: values.profile });
102
+ const loaded = loadConfigWithSource({ profile: values.profile });
103
+ const mode = outputMode(values);
104
+
105
+ const out = {
106
+ profile: selection.name || null,
107
+ origin: selection.origin || null,
108
+ source: loaded.source,
109
+ baseUrl: loaded.config.baseUrl || null,
110
+ token: tokenStatus(loaded.config)
111
+ };
112
+
113
+ if (mode === 'json') return printJson(out);
114
+ if (mode === 'yaml') return printYaml(out);
115
+
116
+ if (selection.name) {
117
+ if (selection.missing) {
118
+ warn(`Selected profile "${selection.name}" (via ${selection.origin}) does not exist.`);
119
+ return;
120
+ }
121
+ success(`Active profile: ${selection.name} (selected via ${selection.origin})`);
122
+ } else if (loaded.source) {
123
+ info(`No named profile in effect — using legacy ${loaded.source.kind} credentials.`);
124
+ } else {
125
+ warn('No credentials configured. Run `zeyos login` or `zeyos profile add`.');
126
+ return;
127
+ }
128
+ console.error(` base URL: ${out.baseUrl || '(none)'}`);
129
+ console.error(` token: ${out.token}`);
130
+ }
131
+
132
+ // ── use ────────────────────────────────────────────────────────────────────────
133
+
134
+ function cmdUse(values, name) {
135
+ if (!name) fail('Usage: zeyos profile use <name> [--local]');
136
+ if (!getProfile(name)) failUnknown(name);
137
+
138
+ if (values.local) {
139
+ const path = writeLocalPin(name);
140
+ success(`Pinned profile "${name}" to this project.`);
141
+ console.error(` ${path}`);
142
+ return;
143
+ }
144
+ setActiveProfile(name);
145
+ success(`Active profile: ${name}`);
146
+ }
147
+
148
+ // ── add ────────────────────────────────────────────────────────────────────────
149
+
150
+ function cmdAdd(values, name) {
151
+ if (!name) fail('Usage: zeyos profile add <name> [--base-url <url>] [--client-id <id>] [--secret <secret>] | --from-current');
152
+
153
+ let updates = {};
154
+ if (values['from-current']) {
155
+ const cfg = loadConfigWithSource().config; // whatever is in effect right now
156
+ for (const k of ['baseUrl', 'instance', 'clientId', 'clientSecret', 'accessToken', 'refreshToken', 'expiresAt', 'refreshTokenExpiresAt']) {
157
+ if (cfg[k] != null) updates[k] = cfg[k];
158
+ }
159
+ if (!updates.baseUrl) fail('Nothing to snapshot: no credentials are currently in effect.');
160
+ } else {
161
+ if (values['base-url']) updates.baseUrl = values['base-url'];
162
+ if (values['client-id']) updates.clientId = values['client-id'];
163
+ if (values.secret) updates.clientSecret = values.secret;
164
+ if (Object.keys(updates).length === 0) {
165
+ fail('Provide at least --base-url (and ideally --client-id/--secret), or use --from-current.');
166
+ }
167
+ }
168
+
169
+ const existed = Boolean(getProfile(name));
170
+ upsertProfile(name, updates);
171
+ success(`${existed ? 'Updated' : 'Created'} profile "${name}".`);
172
+ if (!updates.accessToken) {
173
+ info(`Finish authenticating with: zeyos login --profile ${name}`);
174
+ }
175
+ }
176
+
177
+ // ── remove ─────────────────────────────────────────────────────────────────────
178
+
179
+ function cmdRemove(values, name) {
180
+ if (!name) fail('Usage: zeyos profile remove <name>');
181
+ const removed = removeProfile(name);
182
+ if (!removed) failUnknown(name);
183
+ success(`Removed profile "${name}".`);
184
+ const pin = readLocalPin();
185
+ if (pin && pin.name === name) {
186
+ warn(`This project still pins "${name}" (${pin.path}); that pin will no longer resolve.`);
187
+ }
188
+ }
189
+
190
+ // ── helpers ────────────────────────────────────────────────────────────────────
191
+
192
+ /** Human-readable token status from stored creds. Handles seconds or ms expiry. */
193
+ function tokenStatus(creds = {}) {
194
+ if (!creds.accessToken) return 'none';
195
+ const exp = creds.expiresAt;
196
+ if (exp == null) return 'present';
197
+ const expSec = Number(exp) > 2e10 ? Number(exp) / 1000 : Number(exp);
198
+ const now = Math.floor(Date.now() / 1000);
199
+ return expSec < now ? 'expired' : 'ok';
200
+ }
201
+
202
+ function failUnknown(name) {
203
+ const names = Object.keys(listProfiles().profiles);
204
+ const known = names.length ? `Known profiles: ${names.join(', ')}.` : 'No profiles defined yet.';
205
+ fail(`No such profile: "${name}". ${known}`);
206
+ }
207
+
208
+ function fail(message) {
209
+ error(message);
210
+ process.exit(1);
211
+ }
@@ -56,7 +56,7 @@ export async function run(values, positional) {
56
56
  // (optional) JSON body some callers pass positionally instead of via --data.
57
57
  const data = buildRecordPayload(values, positional[2]);
58
58
 
59
- const clientState = buildCliClient();
59
+ const clientState = buildCliClient(values);
60
60
 
61
61
  // ── Call API ───────────────────────────────────────────────────────────────
62
62
  if (await maybeDryRun(clientState, res.update, { ID: id, body: data }, values)) return;
@@ -27,7 +27,7 @@ Options:
27
27
  export async function run(values) {
28
28
  let client, config, tokenStore, configSource;
29
29
  try {
30
- ({ client, config, tokenStore, configSource } = buildClient());
30
+ ({ client, config, tokenStore, configSource } = buildClient({}, { profile: values.profile }));
31
31
  } catch (err) {
32
32
  error(err.message);
33
33
  process.exit(1);
@@ -38,7 +38,14 @@ export async function run(values) {
38
38
  userInfo = await client.oauth2.getUserInfo();
39
39
  await syncTokens(tokenStore, configSource);
40
40
  } catch (err) {
41
- error(`Failed to fetch user info: ${err.message}`);
41
+ const status = err?.status;
42
+ if (status === 502 || status === 503 || status === 504) {
43
+ error(`ZeyOS instance is temporarily unavailable (HTTP ${status}). The server at ${config.baseUrl} may be down or restarting — this is server-side, not your credentials.`);
44
+ } else if (status === 401) {
45
+ error('Your session has expired or is invalid. Re-authenticate with: zeyos login --force');
46
+ } else {
47
+ error(`Failed to fetch user info: ${err.message}`);
48
+ }
42
49
  process.exit(1);
43
50
  }
44
51
 
@@ -0,0 +1,21 @@
1
+ {
2
+ "list": {
3
+ "fields": {
4
+ "ID": "ID",
5
+ "Num": "actionnum",
6
+ "Name": "name",
7
+ "Status": "status",
8
+ "Date": "date",
9
+ "Due": "duedate",
10
+ "Effort": "effort",
11
+ "Ticket": "ticket",
12
+ "Task": "task",
13
+ "Account": "account",
14
+ "Transaction": "transaction"
15
+ }
16
+ },
17
+ "get": {
18
+ "fields": ["ID", "actionnum", "name", "description", "status", "date", "duedate", "effort", "ticket", "task", "account", "transaction", "assigneduser", "creator", "creationdate", "lastmodified"],
19
+ "params": { "extdata": 1 }
20
+ }
21
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "list": {
3
+ "fields": {
4
+ "ID": "ID",
5
+ "Name": "name",
6
+ "Identifier": "identifier",
7
+ "Context": "context",
8
+ "Reference": "reference",
9
+ "Type": "type",
10
+ "Entity": "entity",
11
+ "Activity": "activity"
12
+ }
13
+ },
14
+ "get": {
15
+ "fields": ["ID", "name", "identifier", "context", "source", "reference", "indexed", "type", "pattern", "entity", "options", "langaliases", "activity"]
16
+ }
17
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "list": {
3
+ "fields": {
4
+ "ID": "ID",
5
+ "Date": "date",
6
+ "Mailbox": "mailbox",
7
+ "Subject": "subject",
8
+ "From": "sender_email",
9
+ "To": "to_email",
10
+ "Ticket": "ticket",
11
+ "Opportunity": "opportunity",
12
+ "Reference": "reference",
13
+ "Message-ID": "messageid"
14
+ }
15
+ },
16
+ "get": {
17
+ "fields": ["ID", "date", "mailbox", "subject", "sender", "sender_email", "sender_name", "to", "to_email", "to_name", "cc", "bcc", "ticket", "opportunity", "reference", "messageid", "contenttype", "text", "attachments", "senddate", "senderror", "creationdate", "lastmodified"],
18
+ "params": { "extdata": 1 }
19
+ }
20
+ }
package/config/task.json CHANGED
@@ -8,11 +8,13 @@
8
8
  "Priority": "priority",
9
9
  "Agent": "assigneduser.name",
10
10
  "Due": "duedate",
11
- "Ticket": "ticket"
11
+ "Ticket": "ticket",
12
+ "Project": "project",
13
+ "Projected": "projectedeffort"
12
14
  }
13
15
  },
14
16
  "get": {
15
- "fields": ["ID", "tasknum", "name", "description", "status", "priority", "duedate", "ticket", "creator", "created", "lastmodified"],
17
+ "fields": ["ID", "tasknum", "name", "description", "status", "priority", "datefrom", "duedate", "ticket", "project", "projectedeffort", "assigneduser", "creator", "creationdate", "lastmodified"],
16
18
  "params": { "extdata": 1 }
17
19
  }
18
20
  }
@@ -7,13 +7,14 @@
7
7
  "Status": "status",
8
8
  "Priority": "priority",
9
9
  "Account": "account.lastname",
10
+ "Project": "project",
10
11
  "Agent": "assigneduser.name",
11
12
  "Due": "duedate",
12
13
  "Modified": "lastmodified"
13
14
  }
14
15
  },
15
16
  "get": {
16
- "fields": ["ID", "ticketnum", "name", "description", "status", "priority", "duedate", "created", "creator", "lastmodified"],
17
+ "fields": ["ID", "ticketnum", "name", "description", "status", "priority", "duedate", "account", "project", "assigneduser", "creator", "creationdate", "lastmodified"],
17
18
  "params": { "extdata": 1 }
18
19
  }
19
20
  }
package/lib/client.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  * Also provides a helper that persists refreshed tokens back to the config file.
4
4
  */
5
5
  import { createZeyosClient, MemoryTokenStore } from '@zeyos/client';
6
- import { loadConfigWithSource, saveConfig, requireConfig } from './config.mjs';
6
+ import { loadConfigWithSource, persistTokens, requireConfig, listProfiles } from './config.mjs';
7
7
 
8
8
  /** @typedef {import('./types.mjs').CliConfig} CliConfig */
9
9
 
@@ -12,10 +12,16 @@ import { loadConfigWithSource, saveConfig, requireConfig } from './config.mjs';
12
12
  * Throws a friendly error if required config keys are missing.
13
13
  *
14
14
  * @param {CliConfig} [overrides] Extra config values (e.g. from CLI flags)
15
- * @returns {{ client: ReturnType<typeof createZeyosClient>, config: CliConfig, tokenStore: MemoryTokenStore, configSource: 'local'|'global'|null }}
15
+ * @param {{ profile?: string }} [opts] Profile selector (from --profile / ZEYOS_PROFILE)
16
+ * @returns {{ client: ReturnType<typeof createZeyosClient>, config: CliConfig, tokenStore: MemoryTokenStore, configSource: import('./config.mjs').ConfigSource|null }}
16
17
  */
17
- export function buildClient(overrides = {}) {
18
- const loaded = loadConfigWithSource();
18
+ export function buildClient(overrides = {}, opts = {}) {
19
+ const loaded = loadConfigWithSource({ profile: opts.profile });
20
+ if (loaded.profile?.missing) {
21
+ const names = Object.keys(listProfiles().profiles);
22
+ const known = names.length ? `Known profiles: ${names.join(', ')}.` : 'No profiles defined yet — create one with `zeyos profile add <name>`.';
23
+ throw new Error(`Profile "${loaded.profile.name}" not found (selected via ${loaded.profile.origin}). ${known}`);
24
+ }
19
25
  const config = { ...loaded.config, ...overrides };
20
26
  requireConfig(['baseUrl', 'clientId', 'clientSecret', 'accessToken'], config);
21
27
 
@@ -43,25 +49,28 @@ export function buildClient(overrides = {}) {
43
49
  }
44
50
 
45
51
  /**
46
- * Persist any refreshed tokens back to the credential store.
52
+ * Persist any refreshed tokens back to the credential store the config came from
53
+ * (a named profile, the legacy local auth.json, or the legacy global file).
47
54
  * Call this after API operations to keep tokens up-to-date.
48
55
  *
49
56
  * @param {MemoryTokenStore} tokenStore
50
- * @param {'local'|'global'|null} scope
57
+ * @param {import('./config.mjs').ConfigSource|'local'|'global'|null} source
51
58
  */
52
- export async function syncTokens(tokenStore, scope = 'local') {
53
- if (!scope) {
59
+ export async function syncTokens(tokenStore, source = 'local') {
60
+ if (!source) {
54
61
  return;
55
62
  }
63
+ // Back-compat: a bare 'local'/'global' string still works.
64
+ const resolved = typeof source === 'string' ? { kind: source } : source;
56
65
  try {
57
66
  const ts = await tokenStore.get();
58
67
  if (ts?.accessToken) {
59
- saveConfig({
68
+ persistTokens(resolved, {
60
69
  accessToken: ts.accessToken,
61
70
  refreshToken: ts.refreshToken,
62
71
  expiresAt: ts.expiresAt,
63
72
  refreshTokenExpiresAt: ts.refreshTokenExpiresAt,
64
- }, scope);
73
+ });
65
74
  }
66
75
  } catch {
67
76
  // non-critical
package/lib/command.mjs CHANGED
@@ -33,9 +33,9 @@ export function requireRecordId(id, usage) {
33
33
  }
34
34
  }
35
35
 
36
- export function buildCliClient() {
36
+ export function buildCliClient(values = {}) {
37
37
  try {
38
- return buildClient();
38
+ return buildClient({}, { profile: values.profile });
39
39
  } catch (err) {
40
40
  fail(err.message);
41
41
  }