@gurulu/cli 0.4.3 → 0.4.5
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 +24 -0
- package/dist/commands/audiences.d.ts +4 -0
- package/dist/commands/audiences.js +37 -1
- package/dist/commands/consent.d.ts +27 -0
- package/dist/commands/consent.js +233 -0
- package/dist/commands/db.js +8 -0
- package/dist/commands/events.js +25 -4
- package/dist/commands/heatmap.d.ts +27 -0
- package/dist/commands/heatmap.js +112 -0
- package/dist/commands/identity.d.ts +3 -0
- package/dist/commands/identity.js +138 -1
- package/dist/commands/install.d.ts +16 -3
- package/dist/commands/install.js +389 -30
- package/dist/commands/secrets.d.ts +19 -0
- package/dist/commands/secrets.js +145 -0
- package/dist/commands/upgrade.d.ts +21 -0
- package/dist/commands/upgrade.js +183 -0
- package/dist/index.js +121 -4
- package/dist/utils/redact.d.ts +14 -0
- package/dist/utils/redact.js +48 -0
- package/package.json +1 -1
- package/scripts/bootstrap-runtime-schema.mjs +7 -25
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI@0.4.4 — `gurulu secrets` — list & rotate keys in the tenant credential vault.
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
* gurulu secrets list (key names only — never values)
|
|
7
|
+
* gurulu secrets rotate --key API_TOKEN (issues new value, keeps old in 24h grace window)
|
|
8
|
+
*
|
|
9
|
+
* TODO(Sprint K): backend endpoints below are not yet live. The CLI command
|
|
10
|
+
* surface is implemented now so customers can wire automation.
|
|
11
|
+
* - GET /api/cli/secrets/list -> { secrets: [{ key, lastRotatedAt, rotationStatus }] }
|
|
12
|
+
* - POST /api/cli/secrets/rotate { key } -> { key, newRevealedOnce: string, graceUntil: ISO }
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.secretsCommand = secretsCommand;
|
|
16
|
+
const config_1 = require("../config");
|
|
17
|
+
const api_client_1 = require("../api-client");
|
|
18
|
+
const ui_1 = require("../utils/ui");
|
|
19
|
+
const redact_1 = require("../utils/redact");
|
|
20
|
+
async function secretsCommand(args) {
|
|
21
|
+
// Defensive — never echo args wholesale in any future debug path.
|
|
22
|
+
if (process.env.GURULU_DEBUG === '1') {
|
|
23
|
+
console.log(`[debug] secrets args: ${JSON.stringify((0, redact_1.redactSensitiveArgs)({ ...args }))}`);
|
|
24
|
+
}
|
|
25
|
+
switch (args.action) {
|
|
26
|
+
case 'list':
|
|
27
|
+
return secretsList(args);
|
|
28
|
+
case 'rotate':
|
|
29
|
+
return secretsRotate(args);
|
|
30
|
+
default:
|
|
31
|
+
(0, ui_1.error)(`Unknown action: ${args.action}. Use: list, rotate`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function loadProfileOrExit(args) {
|
|
36
|
+
try {
|
|
37
|
+
return await (0, config_1.loadActiveProfile)({ profile: args.profile });
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
(0, ui_1.error)('Not authenticated. Run "gurulu login" first.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function emitFallbackNotice(json) {
|
|
45
|
+
if (json)
|
|
46
|
+
return;
|
|
47
|
+
(0, ui_1.info)((0, ui_1.dim)('Note: backend endpoint not yet live (Sprint K candidate). Command-shape is stable.'));
|
|
48
|
+
}
|
|
49
|
+
// ── list ───────────────────────────────────────────────────────────────────
|
|
50
|
+
async function secretsList(args) {
|
|
51
|
+
const profile = await loadProfileOrExit(args);
|
|
52
|
+
try {
|
|
53
|
+
const res = await (0, api_client_1.cliApi)('/api/cli/secrets/list', { preloadedProfile: profile });
|
|
54
|
+
const data = await res.json().catch(() => ({}));
|
|
55
|
+
if (res.status === 404) {
|
|
56
|
+
emitFallbackNotice(args.json);
|
|
57
|
+
if (args.json)
|
|
58
|
+
console.log(JSON.stringify({ ok: false, pending: true, secrets: [] }));
|
|
59
|
+
else
|
|
60
|
+
(0, ui_1.info)('Pending: would list tenant secrets (key names only).');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
const msg = data.message || data.error || `HTTP ${res.status}`;
|
|
65
|
+
if (args.json)
|
|
66
|
+
console.log(JSON.stringify({ ok: false, error: msg }));
|
|
67
|
+
else
|
|
68
|
+
(0, ui_1.error)(msg);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
if (args.json) {
|
|
72
|
+
console.log(JSON.stringify(data, null, 2));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const secrets = data.secrets || [];
|
|
76
|
+
if (secrets.length === 0) {
|
|
77
|
+
(0, ui_1.info)('No secrets in this tenant vault.');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log((0, ui_1.bold)('Secrets (values are never exposed):'));
|
|
81
|
+
for (const s of secrets) {
|
|
82
|
+
const rotated = s.lastRotatedAt ? (0, ui_1.dim)(` rotated ${s.lastRotatedAt}`) : '';
|
|
83
|
+
const status = s.rotationStatus ? (0, ui_1.dim)(` [${s.rotationStatus}]`) : '';
|
|
84
|
+
(0, ui_1.step)(`${s.key}${rotated}${status}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (args.json)
|
|
89
|
+
console.log(JSON.stringify({ ok: false, error: err.message }));
|
|
90
|
+
else
|
|
91
|
+
(0, ui_1.error)(`Failed: ${err.message}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// ── rotate ─────────────────────────────────────────────────────────────────
|
|
96
|
+
async function secretsRotate(args) {
|
|
97
|
+
if (!args.key) {
|
|
98
|
+
(0, ui_1.error)('Usage: gurulu secrets rotate --key <KEY_NAME>');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
const profile = await loadProfileOrExit(args);
|
|
102
|
+
try {
|
|
103
|
+
const res = await (0, api_client_1.cliApi)('/api/cli/secrets/rotate', {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
body: JSON.stringify({ key: args.key }),
|
|
106
|
+
preloadedProfile: profile,
|
|
107
|
+
});
|
|
108
|
+
const data = await res.json().catch(() => ({}));
|
|
109
|
+
if (res.status === 404) {
|
|
110
|
+
emitFallbackNotice(args.json);
|
|
111
|
+
if (args.json)
|
|
112
|
+
console.log(JSON.stringify({ ok: false, pending: true, key: args.key }));
|
|
113
|
+
else
|
|
114
|
+
(0, ui_1.info)(`Pending: would rotate ${args.key} (24h grace window).`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
const msg = data.message || data.error || `HTTP ${res.status}`;
|
|
119
|
+
if (args.json)
|
|
120
|
+
console.log(JSON.stringify({ ok: false, error: msg }));
|
|
121
|
+
else
|
|
122
|
+
(0, ui_1.error)(msg);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
if (args.json) {
|
|
126
|
+
console.log(JSON.stringify(data, null, 2));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
(0, ui_1.success)(`Rotated ${args.key}`);
|
|
130
|
+
if (data.newRevealedOnce) {
|
|
131
|
+
(0, ui_1.warn)('New value (shown ONCE — store it now, you cannot retrieve it again):');
|
|
132
|
+
console.log(` ${(0, ui_1.bold)(data.newRevealedOnce)}`);
|
|
133
|
+
}
|
|
134
|
+
if (data.graceUntil) {
|
|
135
|
+
(0, ui_1.info)(`Old value remains valid until: ${data.graceUntil}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
if (args.json)
|
|
140
|
+
console.log(JSON.stringify({ ok: false, error: err.message }));
|
|
141
|
+
else
|
|
142
|
+
(0, ui_1.error)(`Failed: ${err.message}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FA-1 P1-4 — `gurulu upgrade` command.
|
|
3
|
+
*
|
|
4
|
+
* Bumps installed @gurulu/* packages to the latest version published on npm.
|
|
5
|
+
* Reads current versions from `npm ls --depth=0 --json` so it works regardless
|
|
6
|
+
* of which package manager is in use, then dispatches to the same `pm`
|
|
7
|
+
* detection install.ts uses (npm/pnpm/yarn/bun) for the actual upgrade.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* gurulu upgrade # default: bump @gurulu/web
|
|
11
|
+
* gurulu upgrade --package=@gurulu/cli
|
|
12
|
+
* gurulu upgrade --all # bump @gurulu/cli + @gurulu/node + @gurulu/web
|
|
13
|
+
* gurulu upgrade --dry-run # show plan without executing
|
|
14
|
+
*/
|
|
15
|
+
export interface UpgradeArgs {
|
|
16
|
+
package?: string;
|
|
17
|
+
all?: boolean;
|
|
18
|
+
dryRun?: boolean;
|
|
19
|
+
path?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function upgradeCommand(args: UpgradeArgs): Promise<void>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* FA-1 P1-4 — `gurulu upgrade` command.
|
|
4
|
+
*
|
|
5
|
+
* Bumps installed @gurulu/* packages to the latest version published on npm.
|
|
6
|
+
* Reads current versions from `npm ls --depth=0 --json` so it works regardless
|
|
7
|
+
* of which package manager is in use, then dispatches to the same `pm`
|
|
8
|
+
* detection install.ts uses (npm/pnpm/yarn/bun) for the actual upgrade.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* gurulu upgrade # default: bump @gurulu/web
|
|
12
|
+
* gurulu upgrade --package=@gurulu/cli
|
|
13
|
+
* gurulu upgrade --all # bump @gurulu/cli + @gurulu/node + @gurulu/web
|
|
14
|
+
* gurulu upgrade --dry-run # show plan without executing
|
|
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.upgradeCommand = upgradeCommand;
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const child_process_1 = require("child_process");
|
|
53
|
+
const ui_1 = require("../utils/ui");
|
|
54
|
+
const install_1 = require("./install");
|
|
55
|
+
const DEFAULT_PACKAGES = ['@gurulu/cli', '@gurulu/node', '@gurulu/web'];
|
|
56
|
+
function runCmd(cmd, args, opts = {}) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const child = (0, child_process_1.spawn)(cmd, args, {
|
|
59
|
+
cwd: opts.cwd,
|
|
60
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
61
|
+
shell: process.platform === 'win32',
|
|
62
|
+
});
|
|
63
|
+
let stdout = '';
|
|
64
|
+
let stderr = '';
|
|
65
|
+
child.stdout.on('data', (c) => (stdout += c.toString()));
|
|
66
|
+
child.stderr.on('data', (c) => (stderr += c.toString()));
|
|
67
|
+
child.on('close', (code) => resolve({ code: code ?? 0, stdout, stderr }));
|
|
68
|
+
child.on('error', (err) => resolve({ code: 1, stdout, stderr: String(err) }));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function getCurrentVersion(repoRoot, pkg) {
|
|
72
|
+
// `npm ls --depth=0 --json` works in any project regardless of which PM
|
|
73
|
+
// wrote the lockfile (npm reads node_modules directly). When the package
|
|
74
|
+
// is missing from node_modules the JSON is still valid; we just see no
|
|
75
|
+
// entry under `dependencies`.
|
|
76
|
+
const res = await runCmd('npm', ['ls', pkg, '--depth=0', '--json'], { cwd: repoRoot });
|
|
77
|
+
if (!res.stdout)
|
|
78
|
+
return null;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(res.stdout);
|
|
81
|
+
const dep = parsed.dependencies && parsed.dependencies[pkg];
|
|
82
|
+
if (dep && typeof dep.version === 'string')
|
|
83
|
+
return dep.version;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function getLatestVersion(pkg) {
|
|
91
|
+
const res = await runCmd('npm', ['view', pkg, 'version']);
|
|
92
|
+
if (res.code !== 0)
|
|
93
|
+
return null;
|
|
94
|
+
const v = res.stdout.trim();
|
|
95
|
+
return v || null;
|
|
96
|
+
}
|
|
97
|
+
function pmUpgradeArgs(pm, pkg) {
|
|
98
|
+
if (pm === 'pnpm')
|
|
99
|
+
return ['update', '--latest', pkg];
|
|
100
|
+
if (pm === 'yarn')
|
|
101
|
+
return ['upgrade', `${pkg}@latest`];
|
|
102
|
+
if (pm === 'bun')
|
|
103
|
+
return ['update', pkg];
|
|
104
|
+
return ['install', `${pkg}@latest`];
|
|
105
|
+
}
|
|
106
|
+
function compareSemver(a, b) {
|
|
107
|
+
// Returns -1 if a < b, 0 if equal, 1 if a > b. Strips any leading `v` and
|
|
108
|
+
// pre-release suffixes ("1.2.3-rc.1" → ["1","2","3"]).
|
|
109
|
+
const norm = (s) => s
|
|
110
|
+
.replace(/^v/, '')
|
|
111
|
+
.split('-')[0]
|
|
112
|
+
.split('.')
|
|
113
|
+
.map((n) => parseInt(n, 10) || 0);
|
|
114
|
+
const aa = norm(a);
|
|
115
|
+
const bb = norm(b);
|
|
116
|
+
for (let i = 0; i < Math.max(aa.length, bb.length); i++) {
|
|
117
|
+
const x = aa[i] || 0;
|
|
118
|
+
const y = bb[i] || 0;
|
|
119
|
+
if (x < y)
|
|
120
|
+
return -1;
|
|
121
|
+
if (x > y)
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
async function upgradeCommand(args) {
|
|
127
|
+
const repoRoot = path.resolve(args.path || process.cwd());
|
|
128
|
+
const targets = args.all
|
|
129
|
+
? DEFAULT_PACKAGES
|
|
130
|
+
: [args.package || '@gurulu/web'];
|
|
131
|
+
(0, ui_1.info)(`${(0, ui_1.bold)('gurulu upgrade')} — checking ${targets.length} package(s) in ${(0, ui_1.cyan)(repoRoot)}`);
|
|
132
|
+
const pm = (0, install_1.detectPackageManager)(repoRoot);
|
|
133
|
+
(0, ui_1.step)(`Package manager: ${(0, ui_1.bold)(pm)}`);
|
|
134
|
+
const plans = [];
|
|
135
|
+
for (const pkg of targets) {
|
|
136
|
+
const [current, latest] = await Promise.all([
|
|
137
|
+
getCurrentVersion(repoRoot, pkg),
|
|
138
|
+
getLatestVersion(pkg),
|
|
139
|
+
]);
|
|
140
|
+
plans.push({ pkg, current, latest });
|
|
141
|
+
}
|
|
142
|
+
const toBump = [];
|
|
143
|
+
for (const p of plans) {
|
|
144
|
+
if (!p.latest) {
|
|
145
|
+
(0, ui_1.warn)(`${p.pkg}: could not resolve latest version (npm view failed).`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!p.current) {
|
|
149
|
+
(0, ui_1.info)(`${p.pkg}: not installed — skipping (run \`gurulu install\` first).`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const cmp = compareSemver(p.current, p.latest);
|
|
153
|
+
if (cmp >= 0) {
|
|
154
|
+
(0, ui_1.success)(`${p.pkg}: up-to-date (${p.current})`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
(0, ui_1.info)(`${p.pkg}: ${(0, ui_1.dim)(p.current)} → ${(0, ui_1.cyan)(p.latest)}`);
|
|
158
|
+
toBump.push(p);
|
|
159
|
+
}
|
|
160
|
+
if (toBump.length === 0) {
|
|
161
|
+
(0, ui_1.success)('All packages up-to-date.');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (args.dryRun) {
|
|
165
|
+
(0, ui_1.info)(`Dry-run: would upgrade ${toBump.length} package(s).`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
let failures = 0;
|
|
169
|
+
for (const p of toBump) {
|
|
170
|
+
(0, ui_1.step)(`Upgrading ${p.pkg} via ${pm}...`);
|
|
171
|
+
const res = await runCmd(pm, pmUpgradeArgs(pm, p.pkg), { cwd: repoRoot });
|
|
172
|
+
if (res.code !== 0) {
|
|
173
|
+
failures++;
|
|
174
|
+
(0, ui_1.error)(`${pm} upgrade failed for ${p.pkg} (exit ${res.code}): ${res.stderr.trim()}`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
(0, ui_1.success)(`${p.pkg} upgraded.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (failures > 0) {
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const status_1 = require("./commands/status");
|
|
|
18
18
|
const doctor_1 = require("./commands/doctor");
|
|
19
19
|
const add_server_1 = require("./commands/add-server");
|
|
20
20
|
const install_1 = require("./commands/install");
|
|
21
|
+
const upgrade_1 = require("./commands/upgrade");
|
|
21
22
|
const warehouse_1 = require("./commands/warehouse");
|
|
22
23
|
// Phase 19.5 W2 — Read-surface subcommands
|
|
23
24
|
const audiences_1 = require("./commands/audiences");
|
|
@@ -33,6 +34,8 @@ const audit_1 = require("./commands/audit");
|
|
|
33
34
|
// Goals & Funnels CRUD
|
|
34
35
|
const goals_1 = require("./commands/goals");
|
|
35
36
|
const funnels_1 = require("./commands/funnels");
|
|
37
|
+
// Sprint J-Heat HM10 — heatmap read surface
|
|
38
|
+
const heatmap_1 = require("./commands/heatmap");
|
|
36
39
|
// Gurulu Chat — NL → SQL analytics
|
|
37
40
|
const chat_1 = require("./commands/chat");
|
|
38
41
|
// Error tracking — source map upload
|
|
@@ -48,9 +51,19 @@ const skad_1 = require("./commands/skad");
|
|
|
48
51
|
const errors_1 = require("./commands/errors");
|
|
49
52
|
const replay_1 = require("./commands/replay");
|
|
50
53
|
const conversion_paths_1 = require("./commands/conversion-paths");
|
|
54
|
+
// CLI@0.4.4 — consent + secrets management
|
|
55
|
+
const consent_1 = require("./commands/consent");
|
|
56
|
+
const secrets_1 = require("./commands/secrets");
|
|
51
57
|
(0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
52
58
|
.scriptName('gurulu')
|
|
53
59
|
.option('profile', { type: 'string', describe: 'Use a specific profile (default: personal)' })
|
|
60
|
+
// FA-1 P0-5 — GDPR consent. Also honored via env vars: GURULU_TELEMETRY=off
|
|
61
|
+
// (or 0/false/no) and DO_NOT_TRACK=1, plus persisted choice in
|
|
62
|
+
// ~/.gurulu/config.json (`telemetry: false`).
|
|
63
|
+
.option('no-telemetry', {
|
|
64
|
+
type: 'boolean',
|
|
65
|
+
describe: 'Disable anonymous install telemetry for this run',
|
|
66
|
+
})
|
|
54
67
|
.command('init', 'Set up Gurulu analytics in your project', (y) => y
|
|
55
68
|
.option('site-id', { type: 'string', describe: 'Site ID' })
|
|
56
69
|
.option('token', { type: 'string', describe: 'Site token' })
|
|
@@ -232,6 +245,10 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
232
245
|
.option('skip-env', { type: 'boolean', describe: 'Skip .env file merge' })
|
|
233
246
|
.option('yes', { type: 'boolean', alias: 'y', describe: 'Non-interactive (assume yes)' })
|
|
234
247
|
.option('ingest-url', { type: 'string', describe: 'Override ingest base URL' })
|
|
248
|
+
// FA-1 P0-2 — self-hosted tracker tag override. When set, install.ts
|
|
249
|
+
// forwards this to the agentic-install script so the rendered script
|
|
250
|
+
// tag points at the customer's own asset host instead of gurulu.io/t.js.
|
|
251
|
+
.option('script-src', { type: 'string', describe: 'Override tracker tag <script src> URL (self-hosted)' })
|
|
235
252
|
.option('verify', { type: 'boolean', default: false, describe: 'Live smoke test after install (requires playwright-core)' })
|
|
236
253
|
.option('skip-intent', { type: 'boolean', describe: 'Skip install-time intent discovery' })
|
|
237
254
|
.option('intent-dry-run', { type: 'boolean', describe: 'Show intent proposal without pre-seeding' })
|
|
@@ -268,6 +285,7 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
268
285
|
skipEnv: args['skip-env'],
|
|
269
286
|
yes: args.yes,
|
|
270
287
|
ingestUrl: args['ingest-url'],
|
|
288
|
+
scriptSrc: args['script-src'],
|
|
271
289
|
verify: args['skip-verify'] ? false : args.verify,
|
|
272
290
|
profile: args.profile,
|
|
273
291
|
skipIntent: args['skip-intent'],
|
|
@@ -277,6 +295,20 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
277
295
|
autoProperties: args['auto-properties'],
|
|
278
296
|
});
|
|
279
297
|
})
|
|
298
|
+
.command(
|
|
299
|
+
// FA-1 P1-4 — `gurulu upgrade` bumps installed @gurulu/* packages to the
|
|
300
|
+
// latest version published on npm. Defaults to @gurulu/web; `--all` bumps
|
|
301
|
+
// cli + node + web in one pass.
|
|
302
|
+
'upgrade [path]', 'Upgrade installed Gurulu packages to the latest npm version', (y) => y
|
|
303
|
+
.positional('path', { type: 'string', describe: 'Target project path (default: cwd)' })
|
|
304
|
+
.option('package', { type: 'string', describe: 'Package to upgrade (default: @gurulu/web)' })
|
|
305
|
+
.option('all', { type: 'boolean', describe: 'Upgrade @gurulu/cli + @gurulu/node + @gurulu/web' })
|
|
306
|
+
.option('dry-run', { type: 'boolean', describe: 'Show what would be upgraded' }), (args) => (0, upgrade_1.upgradeCommand)({
|
|
307
|
+
path: args.path,
|
|
308
|
+
package: args.package,
|
|
309
|
+
all: args.all,
|
|
310
|
+
dryRun: args['dry-run'],
|
|
311
|
+
}))
|
|
280
312
|
.command('warehouse <action>', 'Warehouse exports (BigQuery)', (y) => y
|
|
281
313
|
.positional('action', { type: 'string', describe: 'Action: export' })
|
|
282
314
|
.option('tenant', { type: 'string', describe: 'Tenant id (forward-compat)' })
|
|
@@ -293,8 +325,11 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
293
325
|
});
|
|
294
326
|
})
|
|
295
327
|
// ── Phase 19.5 W2 — read-surface subcommands ─────────────────────────
|
|
296
|
-
.command('audiences <action> [target]', 'Manage audiences (list, show, create, update, delete)', (y) => y
|
|
297
|
-
.positional('action', {
|
|
328
|
+
.command('audiences <action> [target]', 'Manage audiences (list, show, create, update, delete, export)', (y) => y
|
|
329
|
+
.positional('action', {
|
|
330
|
+
type: 'string',
|
|
331
|
+
describe: 'list | show | create | update | delete | export',
|
|
332
|
+
})
|
|
298
333
|
.positional('target', { type: 'string', describe: 'Audience name or id' })
|
|
299
334
|
.option('site', { type: 'string', describe: 'Site ID' })
|
|
300
335
|
.option('id', { type: 'string', describe: 'Audience ID (for update/delete)' })
|
|
@@ -304,6 +339,15 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
304
339
|
.option('from-file', { type: 'string', describe: 'Load payload from JSON file' })
|
|
305
340
|
.option('dry-run', { type: 'boolean', describe: 'Show what would be done' })
|
|
306
341
|
.option('yes', { type: 'boolean', alias: 'y', describe: 'Skip confirmation' })
|
|
342
|
+
// AU5 — export options
|
|
343
|
+
.option('format', {
|
|
344
|
+
type: 'string',
|
|
345
|
+
describe: 'Export format: csv | json (default csv)',
|
|
346
|
+
})
|
|
347
|
+
.option('output', {
|
|
348
|
+
type: 'string',
|
|
349
|
+
describe: 'Write export body to this file (default stdout)',
|
|
350
|
+
})
|
|
307
351
|
.option('json', { type: 'boolean', describe: 'JSON output' }), (args) => (0, audiences_1.audiencesCommand)({
|
|
308
352
|
action: args.action,
|
|
309
353
|
target: args.target,
|
|
@@ -315,6 +359,8 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
315
359
|
fromFile: args['from-file'],
|
|
316
360
|
dryRun: args['dry-run'],
|
|
317
361
|
yes: args.yes,
|
|
362
|
+
format: args.format,
|
|
363
|
+
output: args.output,
|
|
318
364
|
json: args.json,
|
|
319
365
|
profile: args.profile,
|
|
320
366
|
}))
|
|
@@ -370,6 +416,10 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
370
416
|
}))
|
|
371
417
|
.command('insights <action>', 'View daily insights (today, history, weekly)', (y) => y
|
|
372
418
|
.positional('action', { type: 'string', describe: 'today | history | weekly' })
|
|
419
|
+
// `--site` is accepted but ignored: insights are computed at tenant
|
|
420
|
+
// scope (across all sites). Accept it so the flag is consistent with
|
|
421
|
+
// the rest of the CLI and doesn't reject scripts that pass it.
|
|
422
|
+
.option('site', { type: 'string', describe: 'Site ID (accepted for consistency, currently tenant-scoped)' })
|
|
373
423
|
.option('days', { type: 'number', describe: 'History window in days (1..30)' })
|
|
374
424
|
.option('json', { type: 'boolean', describe: 'JSON output' }), (args) => (0, insights_1.insightsCommand)({
|
|
375
425
|
action: args.action,
|
|
@@ -465,10 +515,44 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
465
515
|
json: args.json,
|
|
466
516
|
profile: args.profile,
|
|
467
517
|
}))
|
|
468
|
-
.command('
|
|
518
|
+
.command('heatmap <action>', 'Inspect click + scroll heatmaps (list, export)', (y) => y
|
|
519
|
+
.positional('action', { type: 'string', describe: 'list | export' })
|
|
520
|
+
.option('site', { type: 'string', describe: 'Site name or id' })
|
|
521
|
+
.option('page', { type: 'string', describe: 'Page URL (for export)' })
|
|
522
|
+
.option('page-id', {
|
|
523
|
+
type: 'string',
|
|
524
|
+
describe: 'Virtual-page id (HM6, optional)',
|
|
525
|
+
})
|
|
526
|
+
.option('type', {
|
|
527
|
+
type: 'string',
|
|
528
|
+
choices: ['click', 'scroll'],
|
|
529
|
+
describe: 'Heatmap type for export (default click)',
|
|
530
|
+
})
|
|
531
|
+
.option('range', {
|
|
532
|
+
type: 'string',
|
|
533
|
+
choices: ['7d', '30d'],
|
|
534
|
+
describe: 'Lookback window (default 7d)',
|
|
535
|
+
})
|
|
536
|
+
.option('viewport', {
|
|
537
|
+
type: 'string',
|
|
538
|
+
choices: ['mobile', 'tablet', 'desktop'],
|
|
539
|
+
describe: 'Filter to a viewport bucket (HM9)',
|
|
540
|
+
})
|
|
541
|
+
.option('json', { type: 'boolean', describe: 'JSON output' }), (args) => (0, heatmap_1.heatmapCommand)({
|
|
542
|
+
action: args.action,
|
|
543
|
+
site: args.site,
|
|
544
|
+
page: args.page,
|
|
545
|
+
pageId: args['page-id'],
|
|
546
|
+
type: args.type,
|
|
547
|
+
range: args.range,
|
|
548
|
+
viewport: args.viewport,
|
|
549
|
+
json: args.json,
|
|
550
|
+
profile: args.profile,
|
|
551
|
+
}))
|
|
552
|
+
.command('identity <action> [sub]', 'Identity state + writes (decay stats | transfers list | cdc-sources list | identify | alias | merge | bulk)', (y) => y
|
|
469
553
|
.positional('action', {
|
|
470
554
|
type: 'string',
|
|
471
|
-
describe: 'decay | transfers | cdc-sources | identify | alias | merge',
|
|
555
|
+
describe: 'decay | transfers | cdc-sources | identify | alias | merge | bulk',
|
|
472
556
|
})
|
|
473
557
|
.positional('sub', { type: 'string', describe: 'Subaction (stats, list)' })
|
|
474
558
|
.option('direction', { type: 'string', describe: 'outbound | inbound | all' })
|
|
@@ -484,6 +568,10 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
484
568
|
.option('new-user-id', { type: 'string', describe: 'New canonical user id (alias)' })
|
|
485
569
|
.option('canonical-id', { type: 'string', describe: 'Winner canonical profile id (merge)' })
|
|
486
570
|
.option('duplicate-id', { type: 'string', describe: 'Loser canonical profile id (merge)' })
|
|
571
|
+
// bulk
|
|
572
|
+
.option('file', { type: 'string', describe: 'Input file path (bulk)' })
|
|
573
|
+
.option('format', { type: 'string', describe: 'csv | json (bulk; auto-detected from extension)' })
|
|
574
|
+
.option('resume-from', { type: 'number', describe: 'Skip first N records (bulk)' })
|
|
487
575
|
.option('yes', { type: 'boolean', alias: 'y', describe: 'Skip confirmation' })
|
|
488
576
|
.option('json', { type: 'boolean', describe: 'JSON output' }), (args) => (0, identity_1.identityCommand)({
|
|
489
577
|
action: args.action,
|
|
@@ -500,6 +588,9 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
500
588
|
newUserId: args['new-user-id'],
|
|
501
589
|
canonicalId: args['canonical-id'],
|
|
502
590
|
duplicateId: args['duplicate-id'],
|
|
591
|
+
file: args.file,
|
|
592
|
+
format: args.format,
|
|
593
|
+
resumeFrom: args['resume-from'],
|
|
503
594
|
yes: args.yes,
|
|
504
595
|
json: args.json,
|
|
505
596
|
profile: args.profile,
|
|
@@ -731,6 +822,32 @@ const conversion_paths_1 = require("./commands/conversion-paths");
|
|
|
731
822
|
format: args.format,
|
|
732
823
|
json: args.json,
|
|
733
824
|
profile: args.profile,
|
|
825
|
+
}))
|
|
826
|
+
// ── CLI@0.4.4 — consent (per-user GDPR scopes) ───────────────────────
|
|
827
|
+
.command('consent <action>', 'Manage per-user consent (set, get, revoke, check)', (y) => y
|
|
828
|
+
.positional('action', { type: 'string', describe: 'set | get | revoke | check' })
|
|
829
|
+
.option('user-id', { type: 'string', describe: 'User ID' })
|
|
830
|
+
.option('scope', { type: 'string', describe: 'Consent scope (e.g. marketing_external, analytics)' })
|
|
831
|
+
.option('state', { type: 'string', choices: ['granted', 'denied'], describe: 'Consent state (for set)' })
|
|
832
|
+
.option('destination', { type: 'string', describe: 'Destination key (for check, e.g. capi)' })
|
|
833
|
+
.option('json', { type: 'boolean', describe: 'JSON output' }), (args) => (0, consent_1.consentCommand)({
|
|
834
|
+
action: args.action,
|
|
835
|
+
userId: args['user-id'],
|
|
836
|
+
scope: args.scope,
|
|
837
|
+
state: args.state,
|
|
838
|
+
destination: args.destination,
|
|
839
|
+
json: args.json,
|
|
840
|
+
profile: args.profile,
|
|
841
|
+
}))
|
|
842
|
+
// ── CLI@0.4.4 — secrets vault (list, rotate) ─────────────────────────
|
|
843
|
+
.command('secrets <action>', 'Tenant credential vault (list, rotate)', (y) => y
|
|
844
|
+
.positional('action', { type: 'string', describe: 'list | rotate' })
|
|
845
|
+
.option('key', { type: 'string', describe: 'Secret key name (for rotate)' })
|
|
846
|
+
.option('json', { type: 'boolean', describe: 'JSON output' }), (args) => (0, secrets_1.secretsCommand)({
|
|
847
|
+
action: args.action,
|
|
848
|
+
key: args.key,
|
|
849
|
+
json: args.json,
|
|
850
|
+
profile: args.profile,
|
|
734
851
|
}))
|
|
735
852
|
.demandCommand(1, 'Run gurulu --help for available commands')
|
|
736
853
|
.strict()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI@0.4.4 — sensitive-arg redaction helper.
|
|
3
|
+
*
|
|
4
|
+
* Used before any debug/log/telemetry path that might serialize CLI args.
|
|
5
|
+
* Mask any key whose name contains password / token / secret / api-key / authorization
|
|
6
|
+
* (case-insensitive). Returns a shallow clone with offending values replaced
|
|
7
|
+
* by `***REDACTED***` (or `undefined` when the original was falsy, so the log
|
|
8
|
+
* does not imply a value was present).
|
|
9
|
+
*/
|
|
10
|
+
export declare function redactSensitiveArgs<T extends Record<string, unknown>>(args: T): T;
|
|
11
|
+
/**
|
|
12
|
+
* Convenience: produce a JSON string with sensitive fields redacted.
|
|
13
|
+
*/
|
|
14
|
+
export declare function safeStringifyArgs(args: Record<string, unknown>): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI@0.4.4 — sensitive-arg redaction helper.
|
|
4
|
+
*
|
|
5
|
+
* Used before any debug/log/telemetry path that might serialize CLI args.
|
|
6
|
+
* Mask any key whose name contains password / token / secret / api-key / authorization
|
|
7
|
+
* (case-insensitive). Returns a shallow clone with offending values replaced
|
|
8
|
+
* by `***REDACTED***` (or `undefined` when the original was falsy, so the log
|
|
9
|
+
* does not imply a value was present).
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.redactSensitiveArgs = redactSensitiveArgs;
|
|
13
|
+
exports.safeStringifyArgs = safeStringifyArgs;
|
|
14
|
+
const SENSITIVE_KEY_RX = /(password|passwd|pwd|token|secret|api[_-]?key|authorization|auth[_-]?token|access[_-]?key|private[_-]?key|client[_-]?secret|session[_-]?token)/i;
|
|
15
|
+
function redactSensitiveArgs(args) {
|
|
16
|
+
if (!args || typeof args !== 'object')
|
|
17
|
+
return args;
|
|
18
|
+
const out = Array.isArray(args)
|
|
19
|
+
? [...args]
|
|
20
|
+
: { ...args };
|
|
21
|
+
for (const k of Object.keys(out)) {
|
|
22
|
+
const v = out[k];
|
|
23
|
+
if (SENSITIVE_KEY_RX.test(k)) {
|
|
24
|
+
out[k] = v ? '***REDACTED***' : undefined;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
// Recurse into nested objects (but not class instances / Buffers / Dates).
|
|
28
|
+
if (v &&
|
|
29
|
+
typeof v === 'object' &&
|
|
30
|
+
!Buffer.isBuffer(v) &&
|
|
31
|
+
!(v instanceof Date) &&
|
|
32
|
+
(Object.getPrototypeOf(v) === Object.prototype || Array.isArray(v))) {
|
|
33
|
+
out[k] = redactSensitiveArgs(v);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Convenience: produce a JSON string with sensitive fields redacted.
|
|
40
|
+
*/
|
|
41
|
+
function safeStringifyArgs(args) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.stringify(redactSensitiveArgs(args));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return '[unserializable]';
|
|
47
|
+
}
|
|
48
|
+
}
|