@gurulu/cli 0.1.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 +66 -0
- package/bin/gurulu.js +2 -0
- package/dist/api-client.d.ts +27 -0
- package/dist/api-client.js +150 -0
- package/dist/commands/add-server.d.ts +9 -0
- package/dist/commands/add-server.js +155 -0
- package/dist/commands/alerts.d.ts +22 -0
- package/dist/commands/alerts.js +281 -0
- package/dist/commands/api-keys.d.ts +20 -0
- package/dist/commands/api-keys.js +130 -0
- package/dist/commands/audiences.d.ts +16 -0
- package/dist/commands/audiences.js +180 -0
- package/dist/commands/audit.d.ts +20 -0
- package/dist/commands/audit.js +130 -0
- package/dist/commands/auth.d.ts +20 -0
- package/dist/commands/auth.js +214 -0
- package/dist/commands/chat.d.ts +18 -0
- package/dist/commands/chat.js +117 -0
- package/dist/commands/config.d.ts +10 -0
- package/dist/commands/config.js +92 -0
- package/dist/commands/db.d.ts +25 -0
- package/dist/commands/db.js +322 -0
- package/dist/commands/destinations.d.ts +20 -0
- package/dist/commands/destinations.js +191 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.js +318 -0
- package/dist/commands/events.d.ts +27 -0
- package/dist/commands/events.js +147 -0
- package/dist/commands/experiments.d.ts +18 -0
- package/dist/commands/experiments.js +233 -0
- package/dist/commands/identity.d.ts +13 -0
- package/dist/commands/identity.js +107 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +215 -0
- package/dist/commands/insights.d.ts +10 -0
- package/dist/commands/insights.js +65 -0
- package/dist/commands/install.d.ts +233 -0
- package/dist/commands/install.js +920 -0
- package/dist/commands/login.d.ts +20 -0
- package/dist/commands/login.js +170 -0
- package/dist/commands/logout.d.ts +10 -0
- package/dist/commands/logout.js +41 -0
- package/dist/commands/playground.d.ts +11 -0
- package/dist/commands/playground.js +47 -0
- package/dist/commands/sites.d.ts +18 -0
- package/dist/commands/sites.js +139 -0
- package/dist/commands/sourcemap.d.ts +21 -0
- package/dist/commands/sourcemap.js +137 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +136 -0
- package/dist/commands/warehouse.d.ts +20 -0
- package/dist/commands/warehouse.js +65 -0
- package/dist/commands/warehouses.d.ts +17 -0
- package/dist/commands/warehouses.js +182 -0
- package/dist/commands/whoami.d.ts +9 -0
- package/dist/commands/whoami.js +47 -0
- package/dist/config.d.ts +75 -0
- package/dist/config.js +329 -0
- package/dist/frameworks/detect.d.ts +8 -0
- package/dist/frameworks/detect.js +362 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +429 -0
- package/dist/install-intent-proposal.d.ts +99 -0
- package/dist/install-intent-proposal.js +202 -0
- package/dist/utils/api.d.ts +20 -0
- package/dist/utils/api.js +47 -0
- package/dist/utils/config.d.ts +13 -0
- package/dist/utils/config.js +30 -0
- package/dist/utils/confirm.d.ts +17 -0
- package/dist/utils/confirm.js +40 -0
- package/dist/utils/dry-run.d.ts +20 -0
- package/dist/utils/dry-run.js +67 -0
- package/dist/utils/from-file.d.ts +9 -0
- package/dist/utils/from-file.js +72 -0
- package/dist/utils/ui.d.ts +14 -0
- package/dist/utils/ui.js +59 -0
- package/package.json +26 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 20 W3 C4 — `gurulu audit tail` / `gurulu audit export`.
|
|
4
|
+
*
|
|
5
|
+
* Thin wrappers around the new /api/cli/audit/{tail,export} endpoints.
|
|
6
|
+
* Requires a secret key with the `audit:read` scope.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.auditCommand = auditCommand;
|
|
10
|
+
exports.parseSinceFlag = parseSinceFlag;
|
|
11
|
+
const api_client_1 = require("../api-client");
|
|
12
|
+
const ui_1 = require("../utils/ui");
|
|
13
|
+
async function auditCommand(args) {
|
|
14
|
+
const action = args.action || 'tail';
|
|
15
|
+
switch (action) {
|
|
16
|
+
case 'tail':
|
|
17
|
+
return tailCmd(args);
|
|
18
|
+
case 'export':
|
|
19
|
+
return exportCmd(args);
|
|
20
|
+
default:
|
|
21
|
+
(0, ui_1.error)(`Unknown audit action: ${action}`);
|
|
22
|
+
(0, ui_1.info)('Usage: gurulu audit [tail|export]');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Convert short-form windows like "30d" / "24h" to ISO. Exported for
|
|
28
|
+
* tests.
|
|
29
|
+
*/
|
|
30
|
+
function parseSinceFlag(raw) {
|
|
31
|
+
if (!raw)
|
|
32
|
+
return undefined;
|
|
33
|
+
const trimmed = raw.trim();
|
|
34
|
+
const m = trimmed.match(/^(\d+)([mhd])$/);
|
|
35
|
+
if (!m)
|
|
36
|
+
return trimmed; // pass through as ISO
|
|
37
|
+
return trimmed; // backend accepts short form too
|
|
38
|
+
}
|
|
39
|
+
async function tailCmd(args) {
|
|
40
|
+
const qs = new URLSearchParams();
|
|
41
|
+
if (args.since)
|
|
42
|
+
qs.set('since', parseSinceFlag(args.since) ?? '');
|
|
43
|
+
const path = `/api/cli/audit/tail${qs.toString() ? `?${qs.toString()}` : ''}`;
|
|
44
|
+
const res = await (0, api_client_1.cliApi)(path, {
|
|
45
|
+
profile: args.profile,
|
|
46
|
+
headers: { accept: 'text/event-stream' },
|
|
47
|
+
});
|
|
48
|
+
if (!res.body) {
|
|
49
|
+
(0, ui_1.error)('Audit tail returned no body.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
(0, ui_1.info)('Streaming audit events — press Ctrl+C to stop');
|
|
53
|
+
const reader = res.body.getReader?.();
|
|
54
|
+
if (!reader) {
|
|
55
|
+
for await (const chunk of res.body) {
|
|
56
|
+
process.stdout.write(formatFrame(Buffer.from(chunk).toString('utf8'), !!args.json));
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const decoder = new TextDecoder();
|
|
61
|
+
// eslint-disable-next-line no-constant-condition
|
|
62
|
+
while (true) {
|
|
63
|
+
const { done, value } = await reader.read();
|
|
64
|
+
if (done)
|
|
65
|
+
return;
|
|
66
|
+
const text = decoder.decode(value, { stream: true });
|
|
67
|
+
process.stdout.write(formatFrame(text, !!args.json));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function formatFrame(raw, json) {
|
|
71
|
+
if (json)
|
|
72
|
+
return raw;
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const block of raw.split('\n\n')) {
|
|
75
|
+
const lines = block.split('\n');
|
|
76
|
+
let ev = 'message';
|
|
77
|
+
let data = '';
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (line.startsWith('event: '))
|
|
80
|
+
ev = line.slice(7).trim();
|
|
81
|
+
else if (line.startsWith('data: '))
|
|
82
|
+
data += line.slice(6);
|
|
83
|
+
}
|
|
84
|
+
if (data && ev === 'audit_event') {
|
|
85
|
+
try {
|
|
86
|
+
const p = JSON.parse(data);
|
|
87
|
+
out.push(`[${p.createdAt || '-'}] ${p.action || '-'} status=${p.status || '-'} key=${p.secretKeyId || '-'}\n`);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
out.push(`[event] ${data}\n`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out.join('');
|
|
95
|
+
}
|
|
96
|
+
async function exportCmd(args) {
|
|
97
|
+
const qs = new URLSearchParams();
|
|
98
|
+
if (args.since)
|
|
99
|
+
qs.set('since', parseSinceFlag(args.since) ?? '');
|
|
100
|
+
if (args.limit)
|
|
101
|
+
qs.set('limit', String(args.limit));
|
|
102
|
+
const path = `/api/cli/audit/export${qs.toString() ? `?${qs.toString()}` : ''}`;
|
|
103
|
+
const res = await (0, api_client_1.cliApi)(path, {
|
|
104
|
+
profile: args.profile,
|
|
105
|
+
headers: { accept: 'application/x-ndjson' },
|
|
106
|
+
});
|
|
107
|
+
if (!res.body) {
|
|
108
|
+
(0, ui_1.error)('Audit export returned no body.');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const format = args.format || 'jsonl';
|
|
112
|
+
if (format !== 'jsonl') {
|
|
113
|
+
(0, ui_1.error)(`Unsupported export format: ${format} (only jsonl is implemented)`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const reader = res.body.getReader?.();
|
|
117
|
+
if (!reader) {
|
|
118
|
+
for await (const chunk of res.body) {
|
|
119
|
+
process.stdout.write(Buffer.from(chunk));
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// eslint-disable-next-line no-constant-condition
|
|
124
|
+
while (true) {
|
|
125
|
+
const { done, value } = await reader.read();
|
|
126
|
+
if (done)
|
|
127
|
+
return;
|
|
128
|
+
process.stdout.write(Buffer.from(value));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.6 — `gurulu auth` device-link command.
|
|
3
|
+
*
|
|
4
|
+
* Implements the client side of the OAuth 2.0 device authorization grant
|
|
5
|
+
* against `/api/cli/device-link/{start,poll}`. The user-facing flow:
|
|
6
|
+
* 1. POST /start with deviceType=cli + hostname as deviceLabel
|
|
7
|
+
* 2. Print the pairing code + verify URL, try to auto-open the browser
|
|
8
|
+
* 3. Poll /poll every pollIntervalMs until approved / denied / expired
|
|
9
|
+
* 4. On approval, persist the returned secret key into the local profile
|
|
10
|
+
*
|
|
11
|
+
* Backward compat: if the user passes `--key gsk_...` (or `--email` +
|
|
12
|
+
* `--secret-key`), we delegate to the existing `loginCommand` verbatim so
|
|
13
|
+
* the legacy manual flow is unchanged.
|
|
14
|
+
*/
|
|
15
|
+
import { type LoginArgs } from './login';
|
|
16
|
+
export interface AuthArgs extends LoginArgs {
|
|
17
|
+
key?: string;
|
|
18
|
+
noBrowser?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function authCommand(args: AuthArgs): Promise<void>;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 18.6 — `gurulu auth` device-link command.
|
|
4
|
+
*
|
|
5
|
+
* Implements the client side of the OAuth 2.0 device authorization grant
|
|
6
|
+
* against `/api/cli/device-link/{start,poll}`. The user-facing flow:
|
|
7
|
+
* 1. POST /start with deviceType=cli + hostname as deviceLabel
|
|
8
|
+
* 2. Print the pairing code + verify URL, try to auto-open the browser
|
|
9
|
+
* 3. Poll /poll every pollIntervalMs until approved / denied / expired
|
|
10
|
+
* 4. On approval, persist the returned secret key into the local profile
|
|
11
|
+
*
|
|
12
|
+
* Backward compat: if the user passes `--key gsk_...` (or `--email` +
|
|
13
|
+
* `--secret-key`), we delegate to the existing `loginCommand` verbatim so
|
|
14
|
+
* the legacy manual flow is unchanged.
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.authCommand = authCommand;
|
|
51
|
+
const os = __importStar(require("os"));
|
|
52
|
+
const child_process_1 = require("child_process");
|
|
53
|
+
const login_1 = require("./login");
|
|
54
|
+
const config_1 = require("../config");
|
|
55
|
+
const api_client_1 = require("../api-client");
|
|
56
|
+
const ui_1 = require("../utils/ui");
|
|
57
|
+
function tryOpenBrowser(url) {
|
|
58
|
+
const platform = process.platform;
|
|
59
|
+
const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
|
|
60
|
+
try {
|
|
61
|
+
const child = (0, child_process_1.spawn)(cmd, [url], {
|
|
62
|
+
detached: true,
|
|
63
|
+
stdio: 'ignore',
|
|
64
|
+
shell: platform === 'win32',
|
|
65
|
+
});
|
|
66
|
+
child.on('error', () => {
|
|
67
|
+
/* ignore — fall back to manual */
|
|
68
|
+
});
|
|
69
|
+
child.unref();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* ignore — we printed the URL already */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function sleep(ms) {
|
|
76
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
77
|
+
}
|
|
78
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
79
|
+
async function authCommand(args) {
|
|
80
|
+
// Back-compat: legacy manual flow.
|
|
81
|
+
const legacyKey = args.key || args.secretKey || args.apiKey || process.env.GURULU_SECRET_KEY;
|
|
82
|
+
if (legacyKey) {
|
|
83
|
+
await (0, login_1.loginCommand)({
|
|
84
|
+
...args,
|
|
85
|
+
secretKey: legacyKey,
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const profileName = args.profile || 'personal';
|
|
90
|
+
const apiBase = args.apiBase || (0, config_1.defaultApiBase)();
|
|
91
|
+
const deviceLabel = os.hostname() || 'Untitled';
|
|
92
|
+
(0, ui_1.info)('[•] Requesting pairing code...');
|
|
93
|
+
let startBody;
|
|
94
|
+
try {
|
|
95
|
+
const res = await (0, api_client_1.cliApi)('/api/cli/device-link/start', {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
skipAuth: true,
|
|
98
|
+
body: JSON.stringify({ deviceType: 'cli', deviceLabel }),
|
|
99
|
+
preloadedProfile: {
|
|
100
|
+
name: profileName,
|
|
101
|
+
email: 'device-link@gurulu.local',
|
|
102
|
+
secret_key: '',
|
|
103
|
+
api_base: apiBase,
|
|
104
|
+
source: 'env',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const txt = await res.text();
|
|
109
|
+
(0, ui_1.error)(`Could not start device-link (${res.status}): ${txt}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
startBody = (await res.json());
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
(0, ui_1.error)(`Could not start device-link: ${err.message}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const { code, verifyUrl, expiresAt, pollIntervalMs } = startBody;
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(` ${(0, ui_1.dim)('Pairing code:')} ${code}`);
|
|
121
|
+
console.log(` ${(0, ui_1.dim)('Visit:')} ${verifyUrl}`);
|
|
122
|
+
console.log(` ${(0, ui_1.dim)('Device:')} CLI — ${deviceLabel}`);
|
|
123
|
+
console.log('');
|
|
124
|
+
if (!args.noBrowser) {
|
|
125
|
+
tryOpenBrowser(verifyUrl);
|
|
126
|
+
}
|
|
127
|
+
const expiresAtMs = new Date(expiresAt).getTime();
|
|
128
|
+
const interval = Math.max(1000, pollIntervalMs || 2000);
|
|
129
|
+
const stdout = process.stdout;
|
|
130
|
+
const isTTY = !!stdout.isTTY;
|
|
131
|
+
let frame = 0;
|
|
132
|
+
let approved = null;
|
|
133
|
+
while (Date.now() < expiresAtMs) {
|
|
134
|
+
if (isTTY) {
|
|
135
|
+
stdout.write(`\r${SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length]} Waiting for approval...`);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const res = await (0, api_client_1.cliApi)('/api/cli/device-link/poll', {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
skipAuth: true,
|
|
141
|
+
body: JSON.stringify({ code }),
|
|
142
|
+
preloadedProfile: {
|
|
143
|
+
name: profileName,
|
|
144
|
+
email: 'device-link@gurulu.local',
|
|
145
|
+
secret_key: '',
|
|
146
|
+
api_base: apiBase,
|
|
147
|
+
source: 'env',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const json = (await res.json().catch(() => ({})));
|
|
151
|
+
if (res.status === 410) {
|
|
152
|
+
if (isTTY)
|
|
153
|
+
stdout.write('\r');
|
|
154
|
+
(0, ui_1.error)('Pairing code expired. Run `gurulu auth` to try again.');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
if (res.status === 404 || ('error' in json && json.error === 'invalid_code')) {
|
|
158
|
+
if (isTTY)
|
|
159
|
+
stdout.write('\r');
|
|
160
|
+
(0, ui_1.error)('Invalid pairing code.');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
if ('status' in json) {
|
|
164
|
+
if (json.status === 'approved') {
|
|
165
|
+
approved = json;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
if (json.status === 'denied') {
|
|
169
|
+
if (isTTY)
|
|
170
|
+
stdout.write('\r');
|
|
171
|
+
(0, ui_1.error)('Pairing request denied. Exiting.');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
if (json.status === 'expired') {
|
|
175
|
+
if (isTTY)
|
|
176
|
+
stdout.write('\r');
|
|
177
|
+
(0, ui_1.error)('Pairing code expired. Run `gurulu auth` to try again.');
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
// pending → continue
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
// Transient network hiccup — continue polling until expiry.
|
|
185
|
+
if (isTTY)
|
|
186
|
+
stdout.write('\r');
|
|
187
|
+
(0, ui_1.info)(`(poll error, retrying: ${err.message})`);
|
|
188
|
+
}
|
|
189
|
+
await sleep(interval);
|
|
190
|
+
}
|
|
191
|
+
if (isTTY)
|
|
192
|
+
stdout.write('\r');
|
|
193
|
+
if (!approved) {
|
|
194
|
+
(0, ui_1.error)('Timed out waiting for approval. Run `gurulu auth` to try again.');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
const fullKey = approved.secretKey.fullKey;
|
|
198
|
+
const { storedInKeychain } = await (0, config_1.saveProfile)(profileName, {
|
|
199
|
+
email: `cli-${deviceLabel}@gurulu.local`,
|
|
200
|
+
secret_key: fullKey,
|
|
201
|
+
api_base: apiBase,
|
|
202
|
+
}, { useKeychain: args.useKeychain ?? (0, config_1.isKeychainAvailable)() });
|
|
203
|
+
const tail = fullKey.slice(-6);
|
|
204
|
+
(0, ui_1.success)(`Device paired. Key …${tail} saved to profile "${profileName}".`);
|
|
205
|
+
if (storedInKeychain) {
|
|
206
|
+
(0, ui_1.info)(` Stored in macOS Keychain (${(0, ui_1.dim)('io.gurulu.cli')})`);
|
|
207
|
+
}
|
|
208
|
+
if (approved.secretKey.expiresAt) {
|
|
209
|
+
(0, ui_1.info)(` Expires: ${approved.secretKey.expiresAt}`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
(0, ui_1.info)(' Expires: never');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gurulu Chat — `gurulu chat` CLI command.
|
|
3
|
+
*
|
|
4
|
+
* Single-shot: `gurulu chat "How many events today?"`
|
|
5
|
+
* Interactive: `gurulu chat` (REPL mode)
|
|
6
|
+
*
|
|
7
|
+
* Flags:
|
|
8
|
+
* --json Machine-readable output
|
|
9
|
+
* --show-sql Also print the generated SQL
|
|
10
|
+
*/
|
|
11
|
+
export interface ChatArgs {
|
|
12
|
+
question?: string;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
showSql?: boolean;
|
|
15
|
+
profile?: string;
|
|
16
|
+
context?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function chatCommand(args: ChatArgs): Promise<void>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gurulu Chat — `gurulu chat` CLI command.
|
|
4
|
+
*
|
|
5
|
+
* Single-shot: `gurulu chat "How many events today?"`
|
|
6
|
+
* Interactive: `gurulu chat` (REPL mode)
|
|
7
|
+
*
|
|
8
|
+
* Flags:
|
|
9
|
+
* --json Machine-readable output
|
|
10
|
+
* --show-sql Also print the generated SQL
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.chatCommand = chatCommand;
|
|
47
|
+
const readline = __importStar(require("readline"));
|
|
48
|
+
const api_client_1 = require("../api-client");
|
|
49
|
+
const ui_1 = require("../utils/ui");
|
|
50
|
+
async function askQuestion(question, args) {
|
|
51
|
+
return (0, api_client_1.cliApiJson)('/api/cli/chat/query', {
|
|
52
|
+
profile: args.profile,
|
|
53
|
+
method: 'POST',
|
|
54
|
+
json: {
|
|
55
|
+
question,
|
|
56
|
+
context: args.context,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function printResult(res, args) {
|
|
61
|
+
if (args.json) {
|
|
62
|
+
process.stdout.write(JSON.stringify(res, null, 2) + '\n');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
process.stdout.write(`\n${res.summary}\n`);
|
|
66
|
+
if (args.showSql) {
|
|
67
|
+
process.stdout.write(`\nSQL: ${res.sql}\n`);
|
|
68
|
+
}
|
|
69
|
+
process.stdout.write(`(${res.rowCount} row${res.rowCount !== 1 ? 's' : ''}${res.cached ? ', cached' : ''})\n\n`);
|
|
70
|
+
}
|
|
71
|
+
async function replMode(args) {
|
|
72
|
+
const rl = readline.createInterface({
|
|
73
|
+
input: process.stdin,
|
|
74
|
+
output: process.stdout,
|
|
75
|
+
prompt: 'gurulu> ',
|
|
76
|
+
});
|
|
77
|
+
(0, ui_1.info)('Gurulu Chat — ask analytics questions in natural language. Type .exit to quit.');
|
|
78
|
+
rl.prompt();
|
|
79
|
+
rl.on('line', async (line) => {
|
|
80
|
+
const trimmed = line.trim();
|
|
81
|
+
if (!trimmed) {
|
|
82
|
+
rl.prompt();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (trimmed === '.exit' || trimmed === '.quit') {
|
|
86
|
+
rl.close();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const res = await askQuestion(trimmed, args);
|
|
91
|
+
printResult(res, args);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
(0, ui_1.error)(err.message || 'Failed to execute query.');
|
|
95
|
+
}
|
|
96
|
+
rl.prompt();
|
|
97
|
+
});
|
|
98
|
+
rl.on('close', () => {
|
|
99
|
+
process.stdout.write('\n');
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async function chatCommand(args) {
|
|
103
|
+
if (args.question) {
|
|
104
|
+
// Single-shot mode
|
|
105
|
+
try {
|
|
106
|
+
const res = await askQuestion(args.question, args);
|
|
107
|
+
printResult(res, args);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
(0, ui_1.error)(err.message || 'Failed to execute query.');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Interactive REPL mode
|
|
116
|
+
await replMode(args);
|
|
117
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.5 W2 — `gurulu config ...` subcommands. No network calls; pure
|
|
3
|
+
* local profile management.
|
|
4
|
+
*/
|
|
5
|
+
export interface ConfigArgs {
|
|
6
|
+
action?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function configCommand(args: ConfigArgs): Promise<void>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 18.5 W2 — `gurulu config ...` subcommands. No network calls; pure
|
|
4
|
+
* local profile management.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.configCommand = configCommand;
|
|
8
|
+
const config_1 = require("../config");
|
|
9
|
+
const ui_1 = require("../utils/ui");
|
|
10
|
+
function maskKey(key) {
|
|
11
|
+
if (!key)
|
|
12
|
+
return '(keychain)';
|
|
13
|
+
if (key.length < 16)
|
|
14
|
+
return '••••••';
|
|
15
|
+
return `${key.slice(0, 9)}•••••••${key.slice(-6)}`;
|
|
16
|
+
}
|
|
17
|
+
async function configCommand(args) {
|
|
18
|
+
const action = args.action || 'list';
|
|
19
|
+
switch (action) {
|
|
20
|
+
case 'list': {
|
|
21
|
+
const profiles = (0, config_1.listProfiles)();
|
|
22
|
+
if (args.json) {
|
|
23
|
+
process.stdout.write(JSON.stringify(profiles, null, 2) + '\n');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (profiles.length === 0) {
|
|
27
|
+
(0, ui_1.info)('No profiles configured. Run `gurulu login` to create one.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const def = (0, config_1.getDefaultProfileName)();
|
|
31
|
+
process.stdout.write(['NAME', 'EMAIL', 'API BASE', 'STORAGE', 'DEFAULT'].join('\t') + '\n');
|
|
32
|
+
for (const p of profiles) {
|
|
33
|
+
process.stdout.write([
|
|
34
|
+
p.name,
|
|
35
|
+
p.email,
|
|
36
|
+
p.api_base,
|
|
37
|
+
p.keychain ? 'keychain' : 'file',
|
|
38
|
+
p.name === def ? '*' : '',
|
|
39
|
+
].join('\t') + '\n');
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
case 'show': {
|
|
44
|
+
const name = args.name || (0, config_1.getDefaultProfileName)();
|
|
45
|
+
const p = (0, config_1.getStoredProfile)(name);
|
|
46
|
+
if (!p) {
|
|
47
|
+
(0, ui_1.error)(`Profile '${name}' not found.`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const out = {
|
|
51
|
+
name,
|
|
52
|
+
email: p.email,
|
|
53
|
+
api_base: p.api_base,
|
|
54
|
+
storage: p.keychain ? 'keychain' : 'file',
|
|
55
|
+
secret_key: maskKey(p.secret_key),
|
|
56
|
+
};
|
|
57
|
+
if (args.json) {
|
|
58
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
process.stdout.write(`Profile: ${name}\n`);
|
|
62
|
+
process.stdout.write(`Email: ${p.email}\n`);
|
|
63
|
+
process.stdout.write(`API base: ${p.api_base}\n`);
|
|
64
|
+
process.stdout.write(`Storage: ${out.storage}\n`);
|
|
65
|
+
process.stdout.write(`Secret key: ${out.secret_key}\n`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
case 'set-default': {
|
|
69
|
+
if (!args.name) {
|
|
70
|
+
(0, ui_1.error)('Usage: gurulu config set-default <profile>');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
(0, config_1.setDefaultProfile)(args.name);
|
|
75
|
+
(0, ui_1.success)(`Default profile set to '${args.name}'.`);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
(0, ui_1.error)(err.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
case 'path': {
|
|
84
|
+
process.stdout.write((0, config_1.configPath)() + '\n');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
(0, ui_1.error)(`Unknown config action: ${action}`);
|
|
89
|
+
(0, ui_1.info)(`Usage: gurulu config [list|show|set-default|path] ${(0, ui_1.dim)('[name]')}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 21 — `gurulu db` — Connect, list, sync, and remove database sources.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* gurulu db connect — connect a Postgres/MySQL database
|
|
6
|
+
* gurulu db list — list connected databases
|
|
7
|
+
* gurulu db sync <id> — trigger manual sync
|
|
8
|
+
* gurulu db remove <id> — remove a connection
|
|
9
|
+
*/
|
|
10
|
+
export interface DbArgs {
|
|
11
|
+
action?: string;
|
|
12
|
+
target?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
host?: string;
|
|
15
|
+
port?: number;
|
|
16
|
+
database?: string;
|
|
17
|
+
user?: string;
|
|
18
|
+
password?: string;
|
|
19
|
+
table?: string;
|
|
20
|
+
schedule?: string;
|
|
21
|
+
json?: boolean;
|
|
22
|
+
profile?: string;
|
|
23
|
+
noInteractive?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function dbCommand(args: DbArgs): Promise<void>;
|