olakai-cli 0.12.0 → 0.13.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/dist/{api-JBVSHCKY.js → api-VIUUUQNR.js} +6 -2
- package/dist/{chunk-KSWV3C7D.js → chunk-CNIGJ52G.js} +4 -4
- package/dist/{chunk-ULJXILXR.js → chunk-HDP2NYQI.js} +3 -3
- package/dist/chunk-HDP2NYQI.js.map +1 -0
- package/dist/{chunk-KY6OHQZW.js → chunk-KMQAGJWN.js} +3 -1
- package/dist/{chunk-KNGRF4XU.js → chunk-X25PWFMN.js} +65 -1
- package/dist/chunk-X25PWFMN.js.map +1 -0
- package/dist/{doctor-7Q7SPMVD.js → doctor-CE634QRJ.js} +5 -5
- package/dist/index.js +467 -31
- package/dist/index.js.map +1 -1
- package/dist/{repair-NIKAW7NS.js → repair-ILYFGYX5.js} +5 -5
- package/dist/{status-IZCIMES2.js → status-FTC6XEQH.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-KNGRF4XU.js.map +0 -1
- package/dist/chunk-ULJXILXR.js.map +0 -1
- /package/dist/{api-JBVSHCKY.js.map → api-VIUUUQNR.js.map} +0 -0
- /package/dist/{chunk-KSWV3C7D.js.map → chunk-CNIGJ52G.js.map} +0 -0
- /package/dist/{chunk-KY6OHQZW.js.map → chunk-KMQAGJWN.js.map} +0 -0
- /package/dist/{doctor-7Q7SPMVD.js.map → doctor-CE634QRJ.js.map} +0 -0
- /package/dist/{repair-NIKAW7NS.js.map → repair-ILYFGYX5.js.map} +0 -0
- /package/dist/{status-IZCIMES2.js.map → status-FTC6XEQH.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
reconcileCurrentWorkspace,
|
|
15
15
|
removeEntry,
|
|
16
16
|
runMonitorInstall
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HDP2NYQI.js";
|
|
18
18
|
import {
|
|
19
|
+
RateLimitError,
|
|
20
|
+
bulkProvisionAgents,
|
|
19
21
|
createAgent,
|
|
20
22
|
createCustomDataConfig,
|
|
21
23
|
createKpi,
|
|
@@ -46,12 +48,19 @@ import {
|
|
|
46
48
|
updateKpi,
|
|
47
49
|
updateWorkflow,
|
|
48
50
|
validateKpiFormula
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-X25PWFMN.js";
|
|
50
52
|
import {
|
|
53
|
+
CLAUDE_DIR,
|
|
54
|
+
HOOK_DEFINITIONS,
|
|
55
|
+
OLAKAI_DIR,
|
|
56
|
+
SETTINGS_FILE,
|
|
51
57
|
findConfiguredWorkspace,
|
|
52
58
|
getLegacyClaudeMonitorConfigPath,
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
getMonitorConfigFileName,
|
|
60
|
+
getMonitorConfigPath,
|
|
61
|
+
mergeHooksSettings,
|
|
62
|
+
writeJsonFile
|
|
63
|
+
} from "./chunk-KMQAGJWN.js";
|
|
55
64
|
import {
|
|
56
65
|
HOSTS,
|
|
57
66
|
clearToken,
|
|
@@ -75,7 +84,6 @@ import {
|
|
|
75
84
|
} from "./chunk-AVB4N2UN.js";
|
|
76
85
|
|
|
77
86
|
// src/index.ts
|
|
78
|
-
import { createRequire } from "module";
|
|
79
87
|
import { Command } from "commander";
|
|
80
88
|
|
|
81
89
|
// src/commands/login.ts
|
|
@@ -160,7 +168,7 @@ Login failed: ${error.message}`);
|
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
function sleep(ms) {
|
|
163
|
-
return new Promise((
|
|
171
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
164
172
|
}
|
|
165
173
|
|
|
166
174
|
// src/commands/logout.ts
|
|
@@ -2324,19 +2332,446 @@ function registerActivityCommand(program2) {
|
|
|
2324
2332
|
activity.command("sessions").description("Inspect chat/session decoration status for troubleshooting").requiredOption("--agent-id <id>", "Agent ID (required)").option("--since <date>", "Start date (ISO format)").option("--until <date>", "End date (ISO format)").option("--limit <n>", "Results per page", "20").option("--offset <n>", "Skip first N results", "0").option("--json", "Output as JSON").action(sessionsCommand);
|
|
2325
2333
|
}
|
|
2326
2334
|
|
|
2327
|
-
// src/commands/
|
|
2335
|
+
// src/commands/admin.ts
|
|
2328
2336
|
import * as fs2 from "fs";
|
|
2337
|
+
import * as path2 from "path";
|
|
2338
|
+
var TOOL_AGENT_SOURCE = {
|
|
2339
|
+
"claude-code": "CLAUDE_CODE"
|
|
2340
|
+
};
|
|
2341
|
+
var UNSUPPORTED_TOOL_HINTS = {
|
|
2342
|
+
codex: "~/.codex/config.toml",
|
|
2343
|
+
cursor: "~/.cursor/hooks.json",
|
|
2344
|
+
"gemini-cli": "~/.gemini/settings.json",
|
|
2345
|
+
antigravity: "~/.antigravity/hooks.json"
|
|
2346
|
+
};
|
|
2347
|
+
var MAX_EMAILS = 500;
|
|
2348
|
+
var CHUNK_SIZE = 100;
|
|
2349
|
+
var RATE_LIMIT_FALLBACK_WAIT_SECONDS = 30;
|
|
2350
|
+
var EMAIL_REGEX2 = /^[^\s@,]+@[^\s@,]+\.[^\s@,]+$/;
|
|
2351
|
+
function parseEmailsFile(content) {
|
|
2352
|
+
const lines = content.split(/\r?\n/);
|
|
2353
|
+
const emails = [];
|
|
2354
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2355
|
+
const invalidLines = [];
|
|
2356
|
+
let firstDataLineSeen = false;
|
|
2357
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2358
|
+
const line = lines[i].trim();
|
|
2359
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
2360
|
+
const firstCell = line.split(",")[0].trim().replace(/^["']|["']$/g, "");
|
|
2361
|
+
if (!firstDataLineSeen) {
|
|
2362
|
+
firstDataLineSeen = true;
|
|
2363
|
+
if (!firstCell.includes("@") && (line.includes(",") || /^["']?email\b/i.test(line))) {
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
if (firstCell.length === 0 || !EMAIL_REGEX2.test(firstCell)) {
|
|
2368
|
+
invalidLines.push({ lineNumber: i + 1, content: line });
|
|
2369
|
+
continue;
|
|
2370
|
+
}
|
|
2371
|
+
const key = firstCell.toLowerCase();
|
|
2372
|
+
if (seen.has(key)) continue;
|
|
2373
|
+
seen.add(key);
|
|
2374
|
+
emails.push(firstCell);
|
|
2375
|
+
}
|
|
2376
|
+
return { emails, invalidLines };
|
|
2377
|
+
}
|
|
2378
|
+
function bundleDirName(email, taken) {
|
|
2379
|
+
const localPart = email.split("@")[0];
|
|
2380
|
+
const sanitized = localPart.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^\.+/, "") || "developer";
|
|
2381
|
+
let candidate = sanitized;
|
|
2382
|
+
let suffix = 2;
|
|
2383
|
+
while (taken.has(candidate.toLowerCase())) {
|
|
2384
|
+
candidate = `${sanitized}-${suffix}`;
|
|
2385
|
+
suffix++;
|
|
2386
|
+
}
|
|
2387
|
+
taken.add(candidate.toLowerCase());
|
|
2388
|
+
return candidate;
|
|
2389
|
+
}
|
|
2390
|
+
function chunkEmails(emails, size = CHUNK_SIZE) {
|
|
2391
|
+
const chunks = [];
|
|
2392
|
+
for (let i = 0; i < emails.length; i += size) {
|
|
2393
|
+
chunks.push(emails.slice(i, i + size));
|
|
2394
|
+
}
|
|
2395
|
+
return chunks;
|
|
2396
|
+
}
|
|
2397
|
+
function mergeBulkProvisionResponses(responses) {
|
|
2398
|
+
const merged = {
|
|
2399
|
+
results: [],
|
|
2400
|
+
summary: { requested: 0, created: 0, reused: 0, rotated: 0, errors: 0 }
|
|
2401
|
+
};
|
|
2402
|
+
for (const response of responses) {
|
|
2403
|
+
merged.results.push(...response.results);
|
|
2404
|
+
merged.summary.requested += response.summary.requested;
|
|
2405
|
+
merged.summary.created += response.summary.created;
|
|
2406
|
+
merged.summary.reused += response.summary.reused;
|
|
2407
|
+
merged.summary.rotated += response.summary.rotated;
|
|
2408
|
+
merged.summary.errors += response.summary.errors;
|
|
2409
|
+
}
|
|
2410
|
+
return merged;
|
|
2411
|
+
}
|
|
2412
|
+
function sleep2(ms) {
|
|
2413
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2414
|
+
}
|
|
2415
|
+
async function provisionChunkWithRetry(payload) {
|
|
2416
|
+
try {
|
|
2417
|
+
return await bulkProvisionAgents(payload);
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
if (!(error instanceof RateLimitError)) throw error;
|
|
2420
|
+
const waitSeconds = error.retryAfterSeconds ?? RATE_LIMIT_FALLBACK_WAIT_SECONDS;
|
|
2421
|
+
console.error(
|
|
2422
|
+
`Rate limited by the backend \u2014 waiting ${waitSeconds}s before retrying this batch\u2026`
|
|
2423
|
+
);
|
|
2424
|
+
await sleep2(waitSeconds * 1e3);
|
|
2425
|
+
return await bulkProvisionAgents(payload);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
function csvEscape(value) {
|
|
2429
|
+
let cell = value;
|
|
2430
|
+
if (/^[=+\-@\t\r]/.test(cell)) {
|
|
2431
|
+
cell = `'${cell}`;
|
|
2432
|
+
}
|
|
2433
|
+
if (/[",\r\n]/.test(cell)) {
|
|
2434
|
+
return `"${cell.replace(/"/g, '""')}"`;
|
|
2435
|
+
}
|
|
2436
|
+
return cell;
|
|
2437
|
+
}
|
|
2438
|
+
function chmod600(filePath) {
|
|
2439
|
+
try {
|
|
2440
|
+
fs2.chmodSync(filePath, 384);
|
|
2441
|
+
} catch {
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
function writeSecretFile(filePath, content) {
|
|
2445
|
+
const dir = path2.dirname(filePath);
|
|
2446
|
+
if (!fs2.existsSync(dir)) {
|
|
2447
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
2448
|
+
}
|
|
2449
|
+
fs2.writeFileSync(filePath, content, { encoding: "utf-8", mode: 384 });
|
|
2450
|
+
chmod600(filePath);
|
|
2451
|
+
}
|
|
2452
|
+
function writeDeveloperBundle(outDir, dirName, result, monitoringEndpoint) {
|
|
2453
|
+
const bundleRoot = path2.join(outDir, dirName);
|
|
2454
|
+
const settingsPath = path2.join(bundleRoot, CLAUDE_DIR, SETTINGS_FILE);
|
|
2455
|
+
writeJsonFile(settingsPath, {
|
|
2456
|
+
hooks: mergeHooksSettings(void 0, HOOK_DEFINITIONS)
|
|
2457
|
+
});
|
|
2458
|
+
const monitorConfig = {
|
|
2459
|
+
agentId: result.agentId ?? "",
|
|
2460
|
+
apiKey: result.apiKey ?? "",
|
|
2461
|
+
agentName: result.agentName ?? "",
|
|
2462
|
+
source: "claude-code",
|
|
2463
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2464
|
+
monitoringEndpoint
|
|
2465
|
+
};
|
|
2466
|
+
const configPath = path2.join(
|
|
2467
|
+
bundleRoot,
|
|
2468
|
+
OLAKAI_DIR,
|
|
2469
|
+
getMonitorConfigFileName("claude-code")
|
|
2470
|
+
);
|
|
2471
|
+
writeSecretFile(configPath, JSON.stringify(monitorConfig, null, 2) + "\n");
|
|
2472
|
+
return [settingsPath, configPath];
|
|
2473
|
+
}
|
|
2474
|
+
function writeKeymaps(outDir, results) {
|
|
2475
|
+
const jsonPath = path2.join(outDir, "keymap.json");
|
|
2476
|
+
const csvPath = path2.join(outDir, "keymap.csv");
|
|
2477
|
+
const rows = results.map((r) => ({
|
|
2478
|
+
email: r.email,
|
|
2479
|
+
agentId: r.agentId ?? "",
|
|
2480
|
+
agentName: r.agentName ?? "",
|
|
2481
|
+
status: r.status,
|
|
2482
|
+
apiKey: r.apiKey ?? ""
|
|
2483
|
+
}));
|
|
2484
|
+
writeSecretFile(jsonPath, JSON.stringify(rows, null, 2) + "\n");
|
|
2485
|
+
const csvLines = ["email,agentId,agentName,status,apiKey"];
|
|
2486
|
+
for (const row of rows) {
|
|
2487
|
+
csvLines.push(
|
|
2488
|
+
[row.email, row.agentId, row.agentName, row.status, row.apiKey].map(csvEscape).join(",")
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
writeSecretFile(csvPath, csvLines.join("\n") + "\n");
|
|
2492
|
+
return [jsonPath, csvPath];
|
|
2493
|
+
}
|
|
2494
|
+
async function bulkProvisionCommand(options) {
|
|
2495
|
+
try {
|
|
2496
|
+
const tool = options.tool;
|
|
2497
|
+
if (!isToolId(tool)) {
|
|
2498
|
+
console.error(
|
|
2499
|
+
`Unknown tool: "${tool}". Bulk provisioning currently supports: claude-code`
|
|
2500
|
+
);
|
|
2501
|
+
process.exit(1);
|
|
2502
|
+
}
|
|
2503
|
+
const source = TOOL_AGENT_SOURCE[tool];
|
|
2504
|
+
if (!source) {
|
|
2505
|
+
const hint = UNSUPPORTED_TOOL_HINTS[tool];
|
|
2506
|
+
console.error(
|
|
2507
|
+
`Bulk provisioning does not support "${tool}" yet.
|
|
2508
|
+
|
|
2509
|
+
${tool} keeps its hooks in a global per-machine file${hint ? ` (${hint})` : ""}, not in per-repo config, so an Intune-pushable per-developer bundle isn't possible for it yet. Only claude-code is supported in v1.`
|
|
2510
|
+
);
|
|
2511
|
+
process.exit(1);
|
|
2512
|
+
}
|
|
2513
|
+
const token = getValidToken();
|
|
2514
|
+
if (!token) {
|
|
2515
|
+
console.error("Not logged in. Run 'olakai login' first.");
|
|
2516
|
+
process.exit(1);
|
|
2517
|
+
}
|
|
2518
|
+
const emailsPath = path2.resolve(options.emails);
|
|
2519
|
+
if (!fs2.existsSync(emailsPath)) {
|
|
2520
|
+
console.error(`Emails file not found: ${emailsPath}`);
|
|
2521
|
+
process.exit(1);
|
|
2522
|
+
}
|
|
2523
|
+
const { emails, invalidLines } = parseEmailsFile(
|
|
2524
|
+
fs2.readFileSync(emailsPath, "utf-8")
|
|
2525
|
+
);
|
|
2526
|
+
if (invalidLines.length > 0) {
|
|
2527
|
+
console.error(
|
|
2528
|
+
`Invalid email lines in ${emailsPath} (fix or remove them, '#' comments are allowed):`
|
|
2529
|
+
);
|
|
2530
|
+
for (const { lineNumber, content } of invalidLines) {
|
|
2531
|
+
console.error(` line ${lineNumber}: ${content}`);
|
|
2532
|
+
}
|
|
2533
|
+
process.exit(1);
|
|
2534
|
+
}
|
|
2535
|
+
if (emails.length === 0) {
|
|
2536
|
+
console.error(`No valid emails found in ${emailsPath}.`);
|
|
2537
|
+
process.exit(1);
|
|
2538
|
+
}
|
|
2539
|
+
if (emails.length > MAX_EMAILS) {
|
|
2540
|
+
console.error(
|
|
2541
|
+
`Too many emails: ${emails.length} (max ${MAX_EMAILS} per run). Split the file and run again.`
|
|
2542
|
+
);
|
|
2543
|
+
process.exit(1);
|
|
2544
|
+
}
|
|
2545
|
+
const outDir = path2.resolve(options.out);
|
|
2546
|
+
fs2.mkdirSync(outDir, { recursive: true });
|
|
2547
|
+
const takenDirs = /* @__PURE__ */ new Set();
|
|
2548
|
+
const dirNameByEmail = /* @__PURE__ */ new Map();
|
|
2549
|
+
for (const email of emails) {
|
|
2550
|
+
dirNameByEmail.set(email.toLowerCase(), bundleDirName(email, takenDirs));
|
|
2551
|
+
}
|
|
2552
|
+
if (!options.yes) {
|
|
2553
|
+
const conflicts = [];
|
|
2554
|
+
for (const name of ["keymap.json", "keymap.csv"]) {
|
|
2555
|
+
const p = path2.join(outDir, name);
|
|
2556
|
+
if (fs2.existsSync(p) && fs2.statSync(p).size > 0) conflicts.push(p);
|
|
2557
|
+
}
|
|
2558
|
+
for (const dirName of dirNameByEmail.values()) {
|
|
2559
|
+
const bundleDir = path2.join(outDir, dirName);
|
|
2560
|
+
if (fs2.existsSync(bundleDir)) conflicts.push(bundleDir);
|
|
2561
|
+
}
|
|
2562
|
+
if (conflicts.length > 0) {
|
|
2563
|
+
console.error(
|
|
2564
|
+
"Refusing to overwrite existing output from a previous run (it may hold API keys / already-deployed configs):"
|
|
2565
|
+
);
|
|
2566
|
+
for (const conflict of conflicts) {
|
|
2567
|
+
console.error(` ${conflict}`);
|
|
2568
|
+
}
|
|
2569
|
+
console.error(
|
|
2570
|
+
"Pass --yes to overwrite, or use a fresh --out directory."
|
|
2571
|
+
);
|
|
2572
|
+
process.exit(1);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
const payloadBase = {
|
|
2576
|
+
source,
|
|
2577
|
+
...options.namePrefix ? { namePrefix: options.namePrefix } : {},
|
|
2578
|
+
rotateExistingKeys: Boolean(options.rotateExistingKeys),
|
|
2579
|
+
setVcsEmailHint: true
|
|
2580
|
+
};
|
|
2581
|
+
const chunks = chunkEmails(emails);
|
|
2582
|
+
const chunkResponses = [];
|
|
2583
|
+
let abort = null;
|
|
2584
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
2585
|
+
const chunk = chunks[i];
|
|
2586
|
+
if (chunks.length > 1) {
|
|
2587
|
+
console.error(
|
|
2588
|
+
`Provisioning batch ${i + 1}/${chunks.length} (${chunk.length} email${chunk.length === 1 ? "" : "s"})\u2026`
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
try {
|
|
2592
|
+
chunkResponses.push(
|
|
2593
|
+
await provisionChunkWithRetry({ ...payloadBase, emails: chunk })
|
|
2594
|
+
);
|
|
2595
|
+
} catch (error) {
|
|
2596
|
+
abort = {
|
|
2597
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
2598
|
+
unattempted: chunks.slice(i).flat()
|
|
2599
|
+
};
|
|
2600
|
+
break;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
const response = mergeBulkProvisionResponses(chunkResponses);
|
|
2604
|
+
const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;
|
|
2605
|
+
const bundlePaths = [];
|
|
2606
|
+
let bundleCount = 0;
|
|
2607
|
+
for (const result of response.results) {
|
|
2608
|
+
if (!result.apiKey) continue;
|
|
2609
|
+
const dirName = dirNameByEmail.get(result.email.toLowerCase()) ?? bundleDirName(result.email, takenDirs);
|
|
2610
|
+
bundlePaths.push(
|
|
2611
|
+
...writeDeveloperBundle(outDir, dirName, result, monitoringEndpoint)
|
|
2612
|
+
);
|
|
2613
|
+
bundleCount++;
|
|
2614
|
+
}
|
|
2615
|
+
const keymapPaths = response.results.length > 0 ? writeKeymaps(outDir, response.results) : [];
|
|
2616
|
+
let unprocessedPath = null;
|
|
2617
|
+
if (abort && abort.unattempted.length > 0) {
|
|
2618
|
+
unprocessedPath = path2.join(outDir, "unprocessed.txt");
|
|
2619
|
+
fs2.writeFileSync(
|
|
2620
|
+
unprocessedPath,
|
|
2621
|
+
abort.unattempted.join("\n") + "\n",
|
|
2622
|
+
"utf-8"
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
if (options.json) {
|
|
2626
|
+
console.log(
|
|
2627
|
+
JSON.stringify(
|
|
2628
|
+
{
|
|
2629
|
+
...response,
|
|
2630
|
+
bundles: [...bundlePaths, ...keymapPaths],
|
|
2631
|
+
...abort ? {
|
|
2632
|
+
aborted: {
|
|
2633
|
+
error: abort.message,
|
|
2634
|
+
unattemptedEmails: abort.unattempted,
|
|
2635
|
+
unprocessedPath
|
|
2636
|
+
}
|
|
2637
|
+
} : {}
|
|
2638
|
+
},
|
|
2639
|
+
null,
|
|
2640
|
+
2
|
|
2641
|
+
)
|
|
2642
|
+
);
|
|
2643
|
+
if (abort || response.summary.errors > 0) {
|
|
2644
|
+
process.exit(1);
|
|
2645
|
+
}
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
const { summary } = response;
|
|
2649
|
+
console.log("");
|
|
2650
|
+
console.log(
|
|
2651
|
+
`Provisioned ${summary.requested} developer${summary.requested === 1 ? "" : "s"} for ${tool}:`
|
|
2652
|
+
);
|
|
2653
|
+
console.log(` created: ${summary.created}`);
|
|
2654
|
+
console.log(` reused: ${summary.reused}`);
|
|
2655
|
+
console.log(` rotated: ${summary.rotated}`);
|
|
2656
|
+
console.log(` errors: ${summary.errors}`);
|
|
2657
|
+
const errorRows = response.results.filter((r) => r.status === "error");
|
|
2658
|
+
if (errorRows.length > 0) {
|
|
2659
|
+
console.log("");
|
|
2660
|
+
console.log("Failed rows:");
|
|
2661
|
+
for (const row of errorRows) {
|
|
2662
|
+
console.log(` ${row.email}: ${row.error ?? "unknown error"}`);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
console.log("");
|
|
2666
|
+
console.log(`\u2713 ${bundleCount} config bundle(s) written to ${outDir}`);
|
|
2667
|
+
if (response.results.length > 0) {
|
|
2668
|
+
console.log(`\u2713 Keymap written to keymap.json / keymap.csv (0600)`);
|
|
2669
|
+
}
|
|
2670
|
+
if (abort) {
|
|
2671
|
+
console.log("");
|
|
2672
|
+
console.log(`\u2717 Provisioning stopped early: ${abort.message}`);
|
|
2673
|
+
console.log(
|
|
2674
|
+
` ${abort.unattempted.length} email(s) were NOT attempted${unprocessedPath ? ` \u2014 written to ${unprocessedPath}` : ""}.`
|
|
2675
|
+
);
|
|
2676
|
+
if (unprocessedPath) {
|
|
2677
|
+
console.log(
|
|
2678
|
+
` Re-run with --emails ${unprocessedPath} --out <fresh-dir> to finish the rollout`
|
|
2679
|
+
);
|
|
2680
|
+
console.log(
|
|
2681
|
+
" (use a fresh --out so this run's keymaps aren't overwritten)."
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
if (summary.reused > 0) {
|
|
2686
|
+
console.log("");
|
|
2687
|
+
console.log(
|
|
2688
|
+
`${summary.reused} developer(s) already had an agent, so no bundle was written for them`
|
|
2689
|
+
);
|
|
2690
|
+
console.log(
|
|
2691
|
+
"(plaintext API keys are only returned at creation). Re-run with"
|
|
2692
|
+
);
|
|
2693
|
+
console.log(
|
|
2694
|
+
"--rotate-existing-keys to regenerate their keys and bundles \u2014 but note"
|
|
2695
|
+
);
|
|
2696
|
+
console.log(
|
|
2697
|
+
"rotation INVALIDATES any configs already deployed to their machines."
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
if (response.results.length > 0) {
|
|
2701
|
+
console.log("");
|
|
2702
|
+
console.log(
|
|
2703
|
+
"\u26A0 These files contain live API credentials. Package the per-developer"
|
|
2704
|
+
);
|
|
2705
|
+
console.log(
|
|
2706
|
+
" directories into your device-management tool (e.g. Intune) so each"
|
|
2707
|
+
);
|
|
2708
|
+
console.log(
|
|
2709
|
+
` developer's repo gets its ${CLAUDE_DIR}/${SETTINGS_FILE} and ${OLAKAI_DIR}/${getMonitorConfigFileName("claude-code")},`
|
|
2710
|
+
);
|
|
2711
|
+
console.log(
|
|
2712
|
+
" then DELETE the local copies (including the keymap files)."
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
if (abort || summary.errors > 0) {
|
|
2716
|
+
process.exit(1);
|
|
2717
|
+
}
|
|
2718
|
+
} catch (error) {
|
|
2719
|
+
console.error(
|
|
2720
|
+
`Error: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2721
|
+
);
|
|
2722
|
+
process.exit(1);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
function registerAdminCommand(program2) {
|
|
2726
|
+
const admin = program2.command("admin").description("Admin operations (require the ADMIN role)");
|
|
2727
|
+
const adminMonitor = admin.command("monitor").description("Admin operations for local coding-agent monitoring");
|
|
2728
|
+
adminMonitor.command("bulk-provision").description(
|
|
2729
|
+
"Bulk-provision developer monitoring agents from an email roster and write Intune-ready per-developer config bundles. Large rosters are provisioned in sequential batches of 100 emails. Exits non-zero when any roster row fails to provision or a batch cannot be completed (bundles/keymaps for provisioned rows are still written; unattempted emails go to <out>/unprocessed.txt)."
|
|
2730
|
+
).requiredOption(
|
|
2731
|
+
"--emails <file>",
|
|
2732
|
+
"Roster file (.txt or .csv): one email per line, or the first CSV column. '#' comments and a header row are skipped."
|
|
2733
|
+
).requiredOption(
|
|
2734
|
+
"--out <dir>",
|
|
2735
|
+
"Output directory for per-developer bundles + keymap.json/keymap.csv"
|
|
2736
|
+
).option(
|
|
2737
|
+
"--tool <tool>",
|
|
2738
|
+
"Tool to provision for (v1: claude-code only)",
|
|
2739
|
+
"claude-code"
|
|
2740
|
+
).option(
|
|
2741
|
+
"--rotate-existing-keys",
|
|
2742
|
+
"Regenerate API keys for developers that already have an agent (invalidates already-deployed configs)"
|
|
2743
|
+
).option(
|
|
2744
|
+
"--name-prefix <prefix>",
|
|
2745
|
+
"Agent name prefix (backend default applies when omitted)"
|
|
2746
|
+
).option("--json", "Output the raw response JSON plus written bundle paths").option(
|
|
2747
|
+
"--yes",
|
|
2748
|
+
"Overwrite existing output in --out (non-empty keymap files and/or per-developer bundle directories from a previous run)"
|
|
2749
|
+
).action(bulkProvisionCommand);
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
// src/commands/monitor.ts
|
|
2753
|
+
import * as fs3 from "fs";
|
|
2754
|
+
|
|
2755
|
+
// src/version.ts
|
|
2756
|
+
import { createRequire } from "module";
|
|
2757
|
+
var require2 = createRequire(import.meta.url);
|
|
2758
|
+
var packageJson = require2("../package.json");
|
|
2759
|
+
function getCliVersion() {
|
|
2760
|
+
return packageJson.version;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
// src/commands/monitor.ts
|
|
2329
2764
|
function readStdin(timeoutMs = 3e3) {
|
|
2330
|
-
return new Promise((
|
|
2765
|
+
return new Promise((resolve2) => {
|
|
2331
2766
|
if (process.stdin.isTTY) {
|
|
2332
|
-
|
|
2767
|
+
resolve2("");
|
|
2333
2768
|
return;
|
|
2334
2769
|
}
|
|
2335
2770
|
let data = "";
|
|
2336
2771
|
const timer = setTimeout(() => {
|
|
2337
2772
|
process.stdin.removeAllListeners();
|
|
2338
2773
|
process.stdin.destroy();
|
|
2339
|
-
|
|
2774
|
+
resolve2(data);
|
|
2340
2775
|
}, timeoutMs);
|
|
2341
2776
|
process.stdin.setEncoding("utf-8");
|
|
2342
2777
|
process.stdin.on("data", (chunk) => {
|
|
@@ -2344,11 +2779,11 @@ function readStdin(timeoutMs = 3e3) {
|
|
|
2344
2779
|
});
|
|
2345
2780
|
process.stdin.on("end", () => {
|
|
2346
2781
|
clearTimeout(timer);
|
|
2347
|
-
|
|
2782
|
+
resolve2(data);
|
|
2348
2783
|
});
|
|
2349
2784
|
process.stdin.on("error", () => {
|
|
2350
2785
|
clearTimeout(timer);
|
|
2351
|
-
|
|
2786
|
+
resolve2(data);
|
|
2352
2787
|
});
|
|
2353
2788
|
});
|
|
2354
2789
|
}
|
|
@@ -2413,7 +2848,7 @@ async function statusCommand(options) {
|
|
|
2413
2848
|
return;
|
|
2414
2849
|
}
|
|
2415
2850
|
if (plugin.id === "claude-code") {
|
|
2416
|
-
const { printClaudeCodeStatus } = await import("./status-
|
|
2851
|
+
const { printClaudeCodeStatus } = await import("./status-FTC6XEQH.js");
|
|
2417
2852
|
await printClaudeCodeStatus({ projectRoot: process.cwd() });
|
|
2418
2853
|
return;
|
|
2419
2854
|
}
|
|
@@ -2462,7 +2897,7 @@ async function disableCommand(options) {
|
|
|
2462
2897
|
}
|
|
2463
2898
|
}
|
|
2464
2899
|
if (agentIdToDelete) {
|
|
2465
|
-
const { deleteAgent: deleteAgent2 } = await import("./api-
|
|
2900
|
+
const { deleteAgent: deleteAgent2 } = await import("./api-VIUUUQNR.js");
|
|
2466
2901
|
try {
|
|
2467
2902
|
await deleteAgent2(agentIdToDelete);
|
|
2468
2903
|
console.log(`\u2713 Remote agent ${agentIdToDelete} deleted.`);
|
|
@@ -2491,7 +2926,7 @@ async function listCommand6(options) {
|
|
|
2491
2926
|
console.log(formatRegistryTable(registry));
|
|
2492
2927
|
}
|
|
2493
2928
|
async function doctorCommand(options) {
|
|
2494
|
-
const { runDoctor, printDoctorResult, exitCodeForStatus } = await import("./doctor-
|
|
2929
|
+
const { runDoctor, printDoctorResult, exitCodeForStatus } = await import("./doctor-CE634QRJ.js");
|
|
2495
2930
|
let tool;
|
|
2496
2931
|
if (!options.all) {
|
|
2497
2932
|
tool = await resolveToolFromOptions(options.tool, "status");
|
|
@@ -2509,7 +2944,7 @@ async function doctorCommand(options) {
|
|
|
2509
2944
|
}
|
|
2510
2945
|
async function repairCommand(options) {
|
|
2511
2946
|
const tool = await resolveToolFromOptions(options.tool, "init");
|
|
2512
|
-
const { runRepair, formatRepairResult, exitCodeForRepair } = await import("./repair-
|
|
2947
|
+
const { runRepair, formatRepairResult, exitCodeForRepair } = await import("./repair-ILYFGYX5.js");
|
|
2513
2948
|
const result = await runRepair({
|
|
2514
2949
|
tool,
|
|
2515
2950
|
interactive: isInteractive()
|
|
@@ -2569,7 +3004,7 @@ function debugInvalidToolFlag(value, event) {
|
|
|
2569
3004
|
{ event, tool: value ?? null }
|
|
2570
3005
|
)}
|
|
2571
3006
|
`;
|
|
2572
|
-
|
|
3007
|
+
fs3.appendFileSync(logPath, line, "utf-8");
|
|
2573
3008
|
} catch {
|
|
2574
3009
|
}
|
|
2575
3010
|
}
|
|
@@ -2581,7 +3016,7 @@ async function postMonitoringPayload(result) {
|
|
|
2581
3016
|
const log = (event, data) => {
|
|
2582
3017
|
if (!debug) return;
|
|
2583
3018
|
try {
|
|
2584
|
-
|
|
3019
|
+
fs3.appendFileSync(
|
|
2585
3020
|
logPath,
|
|
2586
3021
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] dispatcher/${event}: ${JSON.stringify(data)}
|
|
2587
3022
|
`,
|
|
@@ -2597,11 +3032,13 @@ async function postMonitoringPayload(result) {
|
|
|
2597
3032
|
apiKeyPrefix: result.transport.apiKey ? result.transport.apiKey.slice(0, 8) + "..." : null,
|
|
2598
3033
|
bodyBytes: JSON.stringify([result.payload]).length
|
|
2599
3034
|
});
|
|
3035
|
+
result.payload.cliVersion = getCliVersion();
|
|
2600
3036
|
const response = await fetch(result.transport.endpoint, {
|
|
2601
3037
|
method: "POST",
|
|
2602
3038
|
headers: {
|
|
2603
3039
|
"x-api-key": result.transport.apiKey,
|
|
2604
|
-
"Content-Type": "application/json"
|
|
3040
|
+
"Content-Type": "application/json",
|
|
3041
|
+
"User-Agent": `olakai-cli/${getCliVersion()}`
|
|
2605
3042
|
},
|
|
2606
3043
|
body: JSON.stringify([result.payload]),
|
|
2607
3044
|
signal: controller.signal
|
|
@@ -2893,19 +3330,19 @@ function sectionHeader(title) {
|
|
|
2893
3330
|
}
|
|
2894
3331
|
function noProfileMessage(backend, backendError) {
|
|
2895
3332
|
if (backendError === "not logged in") {
|
|
2896
|
-
return "Run `olakai login` to see your
|
|
3333
|
+
return "Run `olakai login` to see your AI Fluency.";
|
|
2897
3334
|
}
|
|
2898
3335
|
if (backendError === "session expired") {
|
|
2899
3336
|
return "Session expired. Run `olakai login` to refresh your credentials.";
|
|
2900
3337
|
}
|
|
2901
3338
|
if (backendError === "unreachable") {
|
|
2902
|
-
return "Could not reach Olakai. Your
|
|
3339
|
+
return "Could not reach Olakai. Your AI Fluency will be here once you're back online.";
|
|
2903
3340
|
}
|
|
2904
3341
|
if (!backend || !backend.found || !backend.profile) {
|
|
2905
3342
|
if (backend?.reason === "opted_out") {
|
|
2906
|
-
return "Your
|
|
3343
|
+
return "Your AI Fluency is turned off. Re-enable it from your Olakai settings to start seeing your digest.";
|
|
2907
3344
|
}
|
|
2908
|
-
return "Not enough coding sessions yet to build your profile. Keep working with monitoring on \u2014 your
|
|
3345
|
+
return "Not enough coding sessions yet to build your profile. Keep working with monitoring on \u2014 your AI Fluency appears after your first few sessions are analyzed.";
|
|
2909
3346
|
}
|
|
2910
3347
|
return null;
|
|
2911
3348
|
}
|
|
@@ -2916,7 +3353,7 @@ function renderHuman(backend, backendError, reportUrl) {
|
|
|
2916
3353
|
return;
|
|
2917
3354
|
}
|
|
2918
3355
|
const profile = backend.profile;
|
|
2919
|
-
console.log(sectionHeader("
|
|
3356
|
+
console.log(sectionHeader("AI FLUENCY"));
|
|
2920
3357
|
if (profile.identity.displayName) {
|
|
2921
3358
|
console.log(` ${profile.identity.displayName}`);
|
|
2922
3359
|
}
|
|
@@ -3010,7 +3447,7 @@ async function profileCommand(options) {
|
|
|
3010
3447
|
} else {
|
|
3011
3448
|
backend = result.data;
|
|
3012
3449
|
}
|
|
3013
|
-
const reportUrl = `${getBaseUrl()}/dashboard/coding-iq/
|
|
3450
|
+
const reportUrl = `${getBaseUrl()}/dashboard/coding-iq/my-ai-fluency`;
|
|
3014
3451
|
if (options.json) {
|
|
3015
3452
|
const output = {
|
|
3016
3453
|
backend,
|
|
@@ -3024,7 +3461,7 @@ async function profileCommand(options) {
|
|
|
3024
3461
|
}
|
|
3025
3462
|
|
|
3026
3463
|
// src/commands/status.ts
|
|
3027
|
-
import * as
|
|
3464
|
+
import * as fs4 from "fs";
|
|
3028
3465
|
function formatDollars2(cents) {
|
|
3029
3466
|
return `$${(cents / 100).toFixed(2)}`;
|
|
3030
3467
|
}
|
|
@@ -3058,7 +3495,7 @@ function collectLocalState() {
|
|
|
3058
3495
|
tool: entry.tool,
|
|
3059
3496
|
agentId: entry.agentId,
|
|
3060
3497
|
workspacePath: entry.path,
|
|
3061
|
-
configured:
|
|
3498
|
+
configured: fs4.existsSync(entry.configPath)
|
|
3062
3499
|
}));
|
|
3063
3500
|
}
|
|
3064
3501
|
async function fetchBackendStatus() {
|
|
@@ -3208,10 +3645,8 @@ async function statusCommand2(options) {
|
|
|
3208
3645
|
}
|
|
3209
3646
|
|
|
3210
3647
|
// src/index.ts
|
|
3211
|
-
var require2 = createRequire(import.meta.url);
|
|
3212
|
-
var packageJson = require2("../package.json");
|
|
3213
3648
|
var program = new Command();
|
|
3214
|
-
program.name("olakai").description("Olakai CLI tool").version(
|
|
3649
|
+
program.name("olakai").description("Olakai CLI tool").version(getCliVersion()).option(
|
|
3215
3650
|
"-e, --env <environment>",
|
|
3216
3651
|
`Environment to use (${getValidEnvironments().join(", ")})`,
|
|
3217
3652
|
"production"
|
|
@@ -3257,11 +3692,12 @@ registerKpisCommand(program);
|
|
|
3257
3692
|
registerCustomDataCommand(program);
|
|
3258
3693
|
registerActivityCommand(program);
|
|
3259
3694
|
registerMonitorCommand(program);
|
|
3695
|
+
registerAdminCommand(program);
|
|
3260
3696
|
program.command("status").description("Show your Olakai monitoring state and personal Coding IQ status").option("--json", "Output as JSON (for machine consumption)").option("--workspace-only", "Show local monitor state only (no backend call)").action(
|
|
3261
3697
|
(opts) => statusCommand2(opts)
|
|
3262
3698
|
);
|
|
3263
3699
|
program.command("profile").description(
|
|
3264
|
-
"Show your Olakai
|
|
3700
|
+
"Show your Olakai AI Fluency \u2014 archetype, dimension scores, and personal ROI"
|
|
3265
3701
|
).option("--json", "Output as JSON (for machine consumption)").action((opts) => profileCommand(opts));
|
|
3266
3702
|
registerProfilesCommand(program);
|
|
3267
3703
|
program.parse();
|