olakai-cli 0.8.0 → 0.10.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/index.js CHANGED
@@ -58,6 +58,7 @@ import {
58
58
  getBaseUrl,
59
59
  getEnvironment,
60
60
  getValidEnvironments,
61
+ getValidToken,
61
62
  isTokenValid,
62
63
  isValidEnvironment,
63
64
  loadToken,
@@ -2837,6 +2838,375 @@ function registerProfilesCommand(program2) {
2837
2838
  });
2838
2839
  }
2839
2840
 
2841
+ // src/commands/profile.ts
2842
+ var DIMENSION_ORDER = [
2843
+ { key: "delegation", label: "Delegation" },
2844
+ { key: "direction", label: "Direction" },
2845
+ { key: "verification", label: "Verification" },
2846
+ { key: "product_judgment", label: "Product Judgment" },
2847
+ { key: "preparation", label: "Preparation" },
2848
+ { key: "spend_efficiency", label: "Spend Efficiency" }
2849
+ ];
2850
+ function formatDollars(cents) {
2851
+ return `$${(cents / 100).toFixed(2)}`;
2852
+ }
2853
+ function pad(s, width) {
2854
+ return s.length >= width ? s : s + " ".repeat(width - s.length);
2855
+ }
2856
+ function scoreBar(score) {
2857
+ if (score === null) return "\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7\xB7";
2858
+ const filled = Math.min(10, Math.max(0, Math.round(score)));
2859
+ return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
2860
+ }
2861
+ async function fetchProfile() {
2862
+ const token = getValidToken();
2863
+ if (!token) {
2864
+ return { data: null, error: "not logged in" };
2865
+ }
2866
+ const controller = new AbortController();
2867
+ const timeout = setTimeout(() => controller.abort(), 8e3);
2868
+ try {
2869
+ const response = await fetch(`${getBaseUrl()}/api/coding-iq/me/profile`, {
2870
+ headers: {
2871
+ Authorization: `Bearer ${token}`
2872
+ },
2873
+ signal: controller.signal
2874
+ });
2875
+ if (response.status === 401) {
2876
+ return { data: null, error: "session expired" };
2877
+ }
2878
+ if (!response.ok) {
2879
+ return { data: null, error: "unreachable" };
2880
+ }
2881
+ const data = await response.json();
2882
+ return { data, error: null };
2883
+ } catch {
2884
+ return { data: null, error: "unreachable" };
2885
+ } finally {
2886
+ clearTimeout(timeout);
2887
+ }
2888
+ }
2889
+ var SECTION_WIDTH = 56;
2890
+ function sectionHeader(title) {
2891
+ const line = `\u2500\u2500 ${title} `;
2892
+ return line + "\u2500".repeat(Math.max(0, SECTION_WIDTH - line.length));
2893
+ }
2894
+ function noProfileMessage(backend, backendError) {
2895
+ if (backendError === "not logged in") {
2896
+ return "Run `olakai login` to see your Builder Profile.";
2897
+ }
2898
+ if (backendError === "session expired") {
2899
+ return "Session expired. Run `olakai login` to refresh your credentials.";
2900
+ }
2901
+ if (backendError === "unreachable") {
2902
+ return "Could not reach Olakai. Your Builder Profile will be here once you're back online.";
2903
+ }
2904
+ if (!backend || !backend.found || !backend.profile) {
2905
+ if (backend?.reason === "opted_out") {
2906
+ return "Your Builder Profile is turned off. Re-enable it from your Olakai settings to start seeing your digest.";
2907
+ }
2908
+ return "Not enough coding sessions yet to build your profile. Keep working with monitoring on \u2014 your Builder Profile appears after your first few sessions are analyzed.";
2909
+ }
2910
+ return null;
2911
+ }
2912
+ function renderHuman(backend, backendError, reportUrl) {
2913
+ const message = noProfileMessage(backend, backendError);
2914
+ if (message) {
2915
+ console.log(message);
2916
+ return;
2917
+ }
2918
+ const profile = backend.profile;
2919
+ console.log(sectionHeader("BUILDER PROFILE"));
2920
+ if (profile.identity.displayName) {
2921
+ console.log(` ${profile.identity.displayName}`);
2922
+ }
2923
+ const dominant = profile.archetype.dominant;
2924
+ if (dominant) {
2925
+ const score = dominant.meanScore !== null ? `${dominant.meanScore.toFixed(1)}/10` : "\u2014";
2926
+ console.log(
2927
+ ` ${dominant.archetype} (${score}, ${dominant.evidenceCount} ${dominant.evidenceCount === 1 ? "episode" : "episodes"})`
2928
+ );
2929
+ const secondary = profile.archetype.secondary;
2930
+ if (secondary) {
2931
+ const sScore = secondary.meanScore !== null ? `${secondary.meanScore.toFixed(1)}/10` : "\u2014";
2932
+ console.log(` Secondary: ${secondary.archetype} (${sScore})`);
2933
+ }
2934
+ } else {
2935
+ console.log(" No dominant archetype yet \u2014 not enough evidence.");
2936
+ }
2937
+ if (profile.reducedFidelity) {
2938
+ console.log(" (reduced fidelity \u2014 based on a small number of episodes)");
2939
+ }
2940
+ console.log();
2941
+ console.log(sectionHeader("DIMENSIONS"));
2942
+ for (const { key, label } of DIMENSION_ORDER) {
2943
+ const dim = profile.dimensions[key];
2944
+ if (!dim || dim.meanScore === null || dim.evidenceCount === 0) {
2945
+ console.log(` ${pad(label, 18)} ${scoreBar(null)} no evidence yet`);
2946
+ continue;
2947
+ }
2948
+ console.log(
2949
+ ` ${pad(label, 18)} ${scoreBar(dim.meanScore)} ${dim.meanScore.toFixed(1)}/10`
2950
+ );
2951
+ }
2952
+ console.log();
2953
+ const roi = profile.roi;
2954
+ console.log(sectionHeader("PERSONAL ROI (this month)"));
2955
+ if (roi.personalSpendCents !== null) {
2956
+ console.log(` AI spend: ${formatDollars(roi.personalSpendCents)}`);
2957
+ } else {
2958
+ console.log(" AI spend: \u2014");
2959
+ }
2960
+ if (roi.costPerCommitCents !== null) {
2961
+ console.log(
2962
+ ` Cost per commit: ${formatDollars(roi.costPerCommitCents)}`
2963
+ );
2964
+ } else {
2965
+ console.log(" Cost per commit: insufficient outcome data");
2966
+ }
2967
+ if (roi.leakedSpendCents !== null && roi.leakedSpendCents > 0) {
2968
+ const efficiencyNote = roi.spendEfficiency?.rationale ? ` (${roi.spendEfficiency.rationale})` : "";
2969
+ console.log(
2970
+ ` Top leak: ${formatDollars(roi.leakedSpendCents)} leaked${efficiencyNote}`
2971
+ );
2972
+ } else if (roi.leakedSpendCents === null) {
2973
+ console.log(" Top leak: \u2014");
2974
+ } else {
2975
+ console.log(" Top leak: none detected");
2976
+ }
2977
+ if (roi.roiMultiplier !== null) {
2978
+ console.log(` ROI: ${roi.roiMultiplier.toFixed(1)}x`);
2979
+ } else {
2980
+ console.log(" ROI: insufficient outcome data");
2981
+ }
2982
+ if (roi.secretsIncidentCount > 0) {
2983
+ console.log(
2984
+ ` \u26A0 Secrets: ${roi.secretsIncidentCount} incident${roi.secretsIncidentCount === 1 ? "" : "s"} this month`
2985
+ );
2986
+ }
2987
+ console.log();
2988
+ const edge = profile.growthEdge;
2989
+ if (edge) {
2990
+ console.log(sectionHeader("GROWTH EDGE"));
2991
+ const edgeScore = edge.meanScore !== null ? ` (${edge.meanScore.toFixed(1)}/10)` : "";
2992
+ console.log(` ${edge.archetype}${edgeScore}`);
2993
+ const guidance = !profile.narrativeUnavailable && profile.narrative?.growthEdgeNarrative || edge.citedRationale || null;
2994
+ if (guidance) {
2995
+ console.log(` ${guidance.trim()}`);
2996
+ }
2997
+ if (edge.suggestedPatternKey) {
2998
+ console.log(` Suggested practice: ${edge.suggestedPatternKey}`);
2999
+ }
3000
+ console.log();
3001
+ }
3002
+ console.log(`Full report: ${reportUrl}`);
3003
+ }
3004
+ async function profileCommand(options) {
3005
+ let backend = null;
3006
+ let backendError = null;
3007
+ const result = await fetchProfile();
3008
+ if (result.error) {
3009
+ backendError = result.error;
3010
+ } else {
3011
+ backend = result.data;
3012
+ }
3013
+ const reportUrl = `${getBaseUrl()}/dashboard/coding-iq/builder-profile`;
3014
+ if (options.json) {
3015
+ const output = {
3016
+ backend,
3017
+ backendError,
3018
+ reportUrl
3019
+ };
3020
+ console.log(JSON.stringify(output, null, 2));
3021
+ return;
3022
+ }
3023
+ renderHuman(backend, backendError, reportUrl);
3024
+ }
3025
+
3026
+ // src/commands/status.ts
3027
+ import * as fs3 from "fs";
3028
+ function formatDollars2(cents) {
3029
+ return `$${(cents / 100).toFixed(2)}`;
3030
+ }
3031
+ function formatRelativeTime(isoString) {
3032
+ const then = new Date(isoString).getTime();
3033
+ const now = Date.now();
3034
+ const diffMs = now - then;
3035
+ if (diffMs < 0) return "just now";
3036
+ const diffSec = Math.floor(diffMs / 1e3);
3037
+ if (diffSec < 60) return "just now";
3038
+ const diffMin = Math.floor(diffSec / 60);
3039
+ if (diffMin < 60) return `${diffMin} minute${diffMin === 1 ? "" : "s"} ago`;
3040
+ const diffHr = Math.floor(diffMin / 60);
3041
+ if (diffHr < 24) return `${diffHr} hour${diffHr === 1 ? "" : "s"} ago`;
3042
+ const diffDay = Math.floor(diffHr / 24);
3043
+ return `${diffDay} day${diffDay === 1 ? "" : "s"} ago`;
3044
+ }
3045
+ function progressBar(pct) {
3046
+ const filled = Math.min(10, Math.max(0, Math.round(pct / 10)));
3047
+ return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
3048
+ }
3049
+ function toolLabel(tool) {
3050
+ return tool.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3051
+ }
3052
+ function pad2(s, width) {
3053
+ return s.length >= width ? s : s + " ".repeat(width - s.length);
3054
+ }
3055
+ function collectLocalState() {
3056
+ const registry = readRegistry();
3057
+ return registry.workspaces.map((entry) => ({
3058
+ tool: entry.tool,
3059
+ agentId: entry.agentId,
3060
+ workspacePath: entry.path,
3061
+ configured: fs3.existsSync(entry.configPath)
3062
+ }));
3063
+ }
3064
+ async function fetchBackendStatus() {
3065
+ const token = getValidToken();
3066
+ if (!token) {
3067
+ return { data: null, error: "not logged in" };
3068
+ }
3069
+ const controller = new AbortController();
3070
+ const timeout = setTimeout(() => controller.abort(), 8e3);
3071
+ try {
3072
+ const response = await fetch(`${getBaseUrl()}/api/coding-iq/me/status`, {
3073
+ headers: {
3074
+ Authorization: `Bearer ${token}`
3075
+ },
3076
+ signal: controller.signal
3077
+ });
3078
+ if (response.status === 401) {
3079
+ return { data: null, error: "session expired" };
3080
+ }
3081
+ if (!response.ok) {
3082
+ return { data: null, error: "unreachable" };
3083
+ }
3084
+ const data = await response.json();
3085
+ return { data, error: null };
3086
+ } catch {
3087
+ return { data: null, error: "unreachable" };
3088
+ } finally {
3089
+ clearTimeout(timeout);
3090
+ }
3091
+ }
3092
+ var SECTION_WIDTH2 = 52;
3093
+ function sectionHeader2(title) {
3094
+ const line = `\u2500\u2500 ${title} `;
3095
+ return line + "\u2500".repeat(Math.max(0, SECTION_WIDTH2 - line.length));
3096
+ }
3097
+ function renderHuman2(workspaces, backend, backendError) {
3098
+ console.log(sectionHeader2("MONITORING"));
3099
+ if (workspaces.length === 0) {
3100
+ console.log(
3101
+ " No workspaces configured. Run `olakai monitor init` in a project directory."
3102
+ );
3103
+ } else {
3104
+ for (const ws of workspaces) {
3105
+ const status = ws.configured ? "\u2713 configured" : "\u2717 config missing";
3106
+ console.log(
3107
+ ` ${pad2(toolLabel(ws.tool), 14)} ${pad2(status, 16)} ${ws.workspacePath}`
3108
+ );
3109
+ }
3110
+ }
3111
+ if (backend?.monitoring.lastActivityAt) {
3112
+ console.log(
3113
+ ` Last activity: ${formatRelativeTime(backend.monitoring.lastActivityAt)}`
3114
+ );
3115
+ }
3116
+ console.log();
3117
+ if (!backend) {
3118
+ if (backendError === "not logged in") {
3119
+ console.log(
3120
+ "Run `olakai login` to see personal spend and budget."
3121
+ );
3122
+ } else if (backendError === "session expired") {
3123
+ console.log(
3124
+ "Session expired. Run `olakai login` to refresh your credentials."
3125
+ );
3126
+ } else if (backendError === "unreachable") {
3127
+ console.log("Could not reach Olakai backend.");
3128
+ }
3129
+ return;
3130
+ }
3131
+ const now = /* @__PURE__ */ new Date();
3132
+ const monthName = now.toLocaleString("default", { month: "long" });
3133
+ console.log(sectionHeader2(`PERSONAL USAGE (${monthName} MTD)`));
3134
+ if (!backend.identity.found) {
3135
+ console.log(
3136
+ " Not yet recognized in Olakai Coding IQ \u2014 spend data will appear after your first session is processed."
3137
+ );
3138
+ } else {
3139
+ console.log(
3140
+ ` Sessions: ${backend.monitoring.recentSessionCount}`
3141
+ );
3142
+ console.log(` Est. cost: ${formatDollars2(backend.spend.mtdCents)}`);
3143
+ if (backend.spend.providers.length > 1) {
3144
+ for (const p of backend.spend.providers) {
3145
+ console.log(
3146
+ ` ${pad2(p.provider, 10)} ${formatDollars2(p.mtdCents)}`
3147
+ );
3148
+ }
3149
+ }
3150
+ }
3151
+ console.log();
3152
+ if (backend.budget.found && backend.budget.monthlyLimitCents !== null) {
3153
+ console.log(sectionHeader2("BUDGET"));
3154
+ const limitStr = formatDollars2(backend.budget.monthlyLimitCents);
3155
+ console.log(` Limit: ${limitStr}/month`);
3156
+ if (backend.budget.usagePct !== null) {
3157
+ const pct = backend.budget.usagePct;
3158
+ const bar = progressBar(pct);
3159
+ const spentStr = formatDollars2(backend.spend.mtdCents);
3160
+ console.log(
3161
+ ` Used: ${pct.toFixed(1)}% ${bar} (${spentStr} of ${limitStr})`
3162
+ );
3163
+ }
3164
+ if (backend.spend.forecastMonthEndCents !== null && backend.budget.forecastPct !== null) {
3165
+ const confidence = backend.spend.forecastConfidence === "insufficient_data" ? "insufficient data" : backend.spend.forecastConfidence;
3166
+ console.log(
3167
+ ` Forecast: ~${formatDollars2(backend.spend.forecastMonthEndCents)} by month-end (${confidence} confidence)`
3168
+ );
3169
+ }
3170
+ if (backend.budget.status) {
3171
+ const statusLabel = backend.budget.status === "ok" ? "\u2713 On track" : backend.budget.status === "warning" ? "\u26A0 Approaching limit" : "\u2717 Limit exceeded";
3172
+ console.log(` Status: ${statusLabel}`);
3173
+ }
3174
+ console.log();
3175
+ }
3176
+ if (backend.identity.found) {
3177
+ console.log(sectionHeader2("IDENTITY"));
3178
+ const identityLine = backend.identity.displayName ? `${backend.identity.displayName} (${backend.identity.identityCount} ${backend.identity.identityCount === 1 ? "identity" : "identities"})` : `${backend.identity.identityCount} ${backend.identity.identityCount === 1 ? "identity" : "identities"} recognized`;
3179
+ console.log(` Recognized: ${identityLine}`);
3180
+ if (backend.identity.projectNames.length > 0) {
3181
+ console.log(` Projects: ${backend.identity.projectNames.join(", ")}`);
3182
+ }
3183
+ console.log();
3184
+ }
3185
+ }
3186
+ async function statusCommand2(options) {
3187
+ const workspaces = collectLocalState();
3188
+ let backend = null;
3189
+ let backendError = null;
3190
+ if (!options.workspaceOnly) {
3191
+ const result = await fetchBackendStatus();
3192
+ if (result.error) {
3193
+ backendError = result.error;
3194
+ } else {
3195
+ backend = result.data;
3196
+ }
3197
+ }
3198
+ if (options.json) {
3199
+ const output = {
3200
+ localMonitor: { workspaces },
3201
+ backend,
3202
+ backendError
3203
+ };
3204
+ console.log(JSON.stringify(output, null, 2));
3205
+ return;
3206
+ }
3207
+ renderHuman2(workspaces, backend, backendError);
3208
+ }
3209
+
2840
3210
  // src/index.ts
2841
3211
  var require2 = createRequire(import.meta.url);
2842
3212
  var packageJson = require2("../package.json");
@@ -2887,6 +3257,12 @@ registerKpisCommand(program);
2887
3257
  registerCustomDataCommand(program);
2888
3258
  registerActivityCommand(program);
2889
3259
  registerMonitorCommand(program);
3260
+ 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
+ (opts) => statusCommand2(opts)
3262
+ );
3263
+ program.command("profile").description(
3264
+ "Show your Olakai Builder Profile \u2014 archetype, dimension scores, and personal ROI"
3265
+ ).option("--json", "Output as JSON (for machine consumption)").action((opts) => profileCommand(opts));
2890
3266
  registerProfilesCommand(program);
2891
3267
  program.parse();
2892
3268
  //# sourceMappingURL=index.js.map