@omnitype-code/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/dist/blame.js ADDED
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runBlame = runBlame;
40
+ const child_process_1 = require("child_process");
41
+ const path = __importStar(require("path"));
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const gradient_string_1 = __importDefault(require("gradient-string"));
44
+ const UI_1 = require("./core/UI");
45
+ const ProvenanceResolver_1 = require("./core/ProvenanceResolver");
46
+ const ORIGIN_COLOR = {
47
+ ai: UI_1.COLORS.ai,
48
+ user: UI_1.COLORS.user,
49
+ paste: UI_1.COLORS.paste,
50
+ existing: '#444444',
51
+ };
52
+ const ORIGIN_BG = {
53
+ ai: '#3d0070',
54
+ user: '#003d6b',
55
+ paste: '#4a3000',
56
+ existing: '#1a1a1a',
57
+ };
58
+ const ORIGIN_BADGE = {
59
+ ai: ' AI ',
60
+ user: 'USR ',
61
+ paste: 'PST ',
62
+ existing: 'SRC ',
63
+ };
64
+ function badge(origin, useColor) {
65
+ if (!useColor)
66
+ return `[${ORIGIN_BADGE[origin].trim()}]`;
67
+ return chalk_1.default.bgHex(ORIGIN_BG[origin]).hex(ORIGIN_COLOR[origin]).bold(ORIGIN_BADGE[origin]);
68
+ }
69
+ function shortModel(model) {
70
+ return model
71
+ .replace(/claude-(\w+)-(\d+)-(\d+).*/, 'c-$1')
72
+ .replace(/gpt-(\w+)/, 'gpt-$1')
73
+ .replace(/gemini-(\w+)/, 'gem-$1')
74
+ .substring(0, 12);
75
+ }
76
+ function shortTool(tool) {
77
+ return tool
78
+ .replace('claude-code', 'claude')
79
+ .replace('antigravity', 'ag')
80
+ .substring(0, 10);
81
+ }
82
+ function attrTag(info, useColor) {
83
+ if (!info || info.origin === 'existing')
84
+ return useColor ? chalk_1.default.hex('#333333')('──────────────') : ' ';
85
+ const parts = [];
86
+ if (info.model)
87
+ parts.push(shortModel(info.model));
88
+ if (info.tool && info.tool !== 'unknown')
89
+ parts.push(shortTool(info.tool));
90
+ const tag = parts.join(chalk_1.default.hex('#555555')('·'));
91
+ return useColor ? chalk_1.default.hex(ORIGIN_COLOR[info.origin])(tag.padEnd(14)) : tag.padEnd(14);
92
+ }
93
+ function barLine(label, count, total, color, width = 24) {
94
+ const pct = total === 0 ? 0 : count / total;
95
+ const filled = Math.round(pct * width);
96
+ const bar = chalk_1.default.hex(color)('█'.repeat(filled)) + chalk_1.default.hex('#222222')('█'.repeat(width - filled));
97
+ const pctStr = chalk_1.default.hex(color).bold(`${Math.round(pct * 100)}%`.padStart(4));
98
+ const countStr = chalk_1.default.hex('#555555')(`${count} lines`.padStart(10));
99
+ return ` ${chalk_1.default.hex(color).bold(label.padEnd(8))} ${bar} ${pctStr} ${countStr}`;
100
+ }
101
+ async function runBlame(opts) {
102
+ const filePath = path.resolve(opts.file);
103
+ const repoPath = opts.repoPath ?? findRepoRoot(filePath) ?? process.cwd();
104
+ const relPath = path.relative(repoPath, filePath).replace(/\\/g, '/');
105
+ const useColor = !opts.noColor && process.stdout.isTTY;
106
+ const branch = (0, ProvenanceResolver_1.getCurrentBranch)(repoPath);
107
+ const projectName = detectProjectName(repoPath);
108
+ // ── Header ─────────────────────────────────────────────────────────────────
109
+ if (useColor) {
110
+ const g = (s) => (0, gradient_string_1.default)([UI_1.COLORS.primary, UI_1.COLORS.secondary, UI_1.COLORS.ai])(s);
111
+ process.stdout.write('\n');
112
+ process.stdout.write(chalk_1.default.bold(g(' ◈ OmniType')) + chalk_1.default.hex('#444444')(' │ ') + chalk_1.default.hex('#888888')('code provenance') + '\n');
113
+ process.stdout.write(chalk_1.default.hex('#333333')(' ─────────────────────────────────────────────────────────────') + '\n');
114
+ process.stdout.write(chalk_1.default.hex('#555555')(' ◎ ') + chalk_1.default.hex('#cccccc').bold(path.basename(filePath)) + chalk_1.default.hex('#444444')(` · ${relPath}`) + '\n');
115
+ process.stdout.write(chalk_1.default.hex('#555555')(' ⎇ ') + chalk_1.default.hex('#58a6ff')(branch) + chalk_1.default.hex('#444444')(` · ${projectName}`) + '\n');
116
+ process.stdout.write(chalk_1.default.hex('#333333')(' ─────────────────────────────────────────────────────────────') + '\n\n');
117
+ }
118
+ let blameOut;
119
+ try {
120
+ blameOut = (0, child_process_1.execFileSync)('git', ['-C', repoPath, 'blame', '--line-porcelain', filePath], { encoding: 'utf8' });
121
+ }
122
+ catch (err) {
123
+ UI_1.UI.error(`Git blame failed: ${err}`);
124
+ process.exit(1);
125
+ }
126
+ const lineMap = await (0, ProvenanceResolver_1.resolveFileLinesAsync)(repoPath, relPath, filePath, branch, projectName);
127
+ const blameLines = parsePorcelainBlame(blameOut);
128
+ const stats = { ai: 0, user: 0, paste: 0, existing: 0, total: 0 };
129
+ const modelCounts = {};
130
+ const toolCounts = {};
131
+ for (const bl of blameLines) {
132
+ const info = lineMap.get(bl.lineNum);
133
+ const origin = info?.origin ?? 'existing';
134
+ const sha = useColor ? chalk_1.default.hex('#333333')(bl.shortSha) : bl.shortSha;
135
+ const author = useColor ? chalk_1.default.hex('#555555')(bl.author.slice(0, 9).padEnd(9)) : bl.author.slice(0, 9).padEnd(9);
136
+ const lineNo = useColor ? chalk_1.default.hex('#444444')(bl.lineNum.toString().padStart(4)) : bl.lineNum.toString().padStart(4);
137
+ const bdg = badge(origin, useColor);
138
+ const tag = attrTag(info, useColor);
139
+ const sep = useColor ? chalk_1.default.hex(ORIGIN_COLOR[origin])('▏') : '|';
140
+ const code = origin === 'existing'
141
+ ? (useColor ? chalk_1.default.hex('#555555')(bl.content) : bl.content)
142
+ : bl.content;
143
+ process.stdout.write(`${sha} ${author} ${lineNo} ${bdg} ${tag} ${sep} ${code}\n`);
144
+ stats[origin]++;
145
+ stats.total++;
146
+ if (origin === 'ai') {
147
+ if (info?.model)
148
+ modelCounts[info.model] = (modelCounts[info.model] ?? 0) + 1;
149
+ if (info?.tool)
150
+ toolCounts[info.tool] = (toolCounts[info.tool] ?? 0) + 1;
151
+ }
152
+ }
153
+ if (!opts.showStats || blameLines.length === 0)
154
+ return;
155
+ // ── Stats footer ───────────────────────────────────────────────────────────
156
+ const T = stats.total;
157
+ const lines = [''];
158
+ // Branding
159
+ if (useColor) {
160
+ const g = (s) => (0, gradient_string_1.default)([UI_1.COLORS.primary, UI_1.COLORS.secondary, UI_1.COLORS.ai])(s);
161
+ lines.push(' ' + chalk_1.default.bold(g('◈ OmniType')) + chalk_1.default.hex('#333333')(' · Attribution Report'));
162
+ lines.push(' ' + chalk_1.default.hex('#333333')('─'.repeat(52)));
163
+ }
164
+ else {
165
+ lines.push(' OmniType · Attribution Report');
166
+ lines.push(' ' + '─'.repeat(52));
167
+ }
168
+ lines.push('');
169
+ lines.push(barLine('AI', stats.ai, T, UI_1.COLORS.ai));
170
+ lines.push(barLine('Typed', stats.user, T, UI_1.COLORS.user));
171
+ lines.push(barLine('Pasted', stats.paste, T, UI_1.COLORS.paste));
172
+ lines.push(barLine('Source', stats.existing, T, '#444444'));
173
+ lines.push('');
174
+ if (Object.keys(modelCounts).length > 0) {
175
+ lines.push(' ' + chalk_1.default.hex('#555555').bold('MODELS'));
176
+ for (const [m, count] of Object.entries(modelCounts).sort(([, a], [, b]) => b - a)) {
177
+ const pct = Math.round(count / T * 100);
178
+ lines.push(` ${chalk_1.default.hex(UI_1.COLORS.ai)(m.padEnd(28))} ${chalk_1.default.hex(UI_1.COLORS.ai).bold(`${pct}%`.padStart(4))} ${chalk_1.default.hex('#444444')(`${count} lines`)}`);
179
+ }
180
+ lines.push('');
181
+ }
182
+ if (Object.keys(toolCounts).length > 0) {
183
+ lines.push(' ' + chalk_1.default.hex('#555555').bold('TOOLS'));
184
+ for (const [t, count] of Object.entries(toolCounts).sort(([, a], [, b]) => b - a)) {
185
+ const pct = Math.round(count / T * 100);
186
+ lines.push(` ${chalk_1.default.hex(UI_1.COLORS.primary)(t.padEnd(28))} ${chalk_1.default.hex(UI_1.COLORS.primary).bold(`${pct}%`.padStart(4))} ${chalk_1.default.hex('#444444')(`${count} lines`)}`);
187
+ }
188
+ lines.push('');
189
+ }
190
+ process.stdout.write(lines.join('\n') + '\n');
191
+ }
192
+ function parsePorcelainBlame(raw) {
193
+ const lines = raw.split('\n');
194
+ const result = [];
195
+ const cache = new Map();
196
+ let i = 0;
197
+ while (i < lines.length) {
198
+ const header = lines[i];
199
+ if (!header || header.length < 40) {
200
+ i++;
201
+ continue;
202
+ }
203
+ const sha = header.slice(0, 40);
204
+ const parts = header.split(' ');
205
+ const lineNum = parseInt(parts[2] ?? parts[1], 10);
206
+ if (isNaN(lineNum)) {
207
+ i++;
208
+ continue;
209
+ }
210
+ let author = cache.get(sha)?.author ?? '';
211
+ let date = cache.get(sha)?.date ?? '';
212
+ i++;
213
+ while (i < lines.length && !lines[i].startsWith('\t')) {
214
+ const l = lines[i];
215
+ if (l.startsWith('author ') && !author)
216
+ author = l.slice(7).trim();
217
+ if (l.startsWith('author-time ') && !date) {
218
+ date = new Date(parseInt(l.slice(12), 10) * 1000).toISOString().slice(0, 10);
219
+ }
220
+ i++;
221
+ }
222
+ if (!cache.has(sha))
223
+ cache.set(sha, { author, date });
224
+ const content = (lines[i] ?? '').slice(1);
225
+ i++;
226
+ result.push({ lineNum, shortSha: sha.slice(0, 7), author, date, content });
227
+ }
228
+ return result;
229
+ }
230
+ function findRepoRoot(startPath) {
231
+ return (0, ProvenanceResolver_1.findRepoRoot)(startPath);
232
+ }
233
+ function detectProjectName(repoPath) {
234
+ try {
235
+ const remotes = (0, child_process_1.execFileSync)('git', ['-C', repoPath, 'remote', 'get-url', 'origin'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
236
+ const match = remotes.replace(/\.git$/, '').match(/[/:]([\w.-]+)$/);
237
+ if (match)
238
+ return match[1];
239
+ }
240
+ catch { /* no remote */ }
241
+ return path.basename(repoPath);
242
+ }
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ApiClient = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const zlib = __importStar(require("zlib"));
41
+ const util_1 = require("util");
42
+ const _gzip = (0, util_1.promisify)(zlib.gzip);
43
+ const CONFIG_PATH = path.join(os.homedir(), '.omnitype', 'config.json');
44
+ const DEFAULT_API = 'https://api.imrishav.life';
45
+ class Semaphore {
46
+ constructor(max) {
47
+ this.queue = [];
48
+ this.count = max;
49
+ }
50
+ acquire() {
51
+ if (this.count > 0) {
52
+ this.count--;
53
+ return Promise.resolve();
54
+ }
55
+ return new Promise(res => this.queue.push(res));
56
+ }
57
+ release() {
58
+ const next = this.queue.shift();
59
+ if (next)
60
+ next();
61
+ else
62
+ this.count++;
63
+ }
64
+ }
65
+ class ApiClient {
66
+ constructor() {
67
+ this.config = {};
68
+ this._loadConfig();
69
+ }
70
+ _loadConfig() {
71
+ try {
72
+ this.config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
73
+ }
74
+ catch {
75
+ this.config = {};
76
+ }
77
+ }
78
+ _saveConfig() {
79
+ fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
80
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(this.config, null, 2));
81
+ }
82
+ get apiUrl() {
83
+ return (this.config.apiUrl ?? DEFAULT_API).replace(/\/+$/, '');
84
+ }
85
+ get token() { return this.config.token; }
86
+ get username() { return this.config.username; }
87
+ get isSignedIn() { return !!this.config.token; }
88
+ async login(email, password) {
89
+ const res = await fetch(`${this.apiUrl}/auth/login`, {
90
+ method: 'POST',
91
+ headers: { 'Content-Type': 'application/json' },
92
+ body: JSON.stringify({
93
+ identifier: email, password,
94
+ device_type: 'cli',
95
+ device_name: `omnitype-cli on ${os.hostname()}`,
96
+ }),
97
+ });
98
+ if (!res.ok)
99
+ throw new Error(`Login failed: ${await res.text()}`);
100
+ const data = await res.json();
101
+ this.config.token = data.token;
102
+ this.config.username = data.user.username;
103
+ this._saveConfig();
104
+ return data.user.username;
105
+ }
106
+ async pullProvenance(projectName, paths) {
107
+ if (!this.config.token)
108
+ return null;
109
+ try {
110
+ const body = { project_name: projectName };
111
+ if (paths?.length)
112
+ body.paths = paths;
113
+ const res = await fetch(`${this.apiUrl}/provenance/pull`, {
114
+ method: 'POST',
115
+ headers: {
116
+ 'Authorization': `Bearer ${this.config.token}`,
117
+ 'Content-Type': 'application/json',
118
+ },
119
+ body: JSON.stringify(body),
120
+ });
121
+ if (!res.ok)
122
+ return null;
123
+ const data = await res.json();
124
+ return data.files ?? null;
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
130
+ async getPersonalStats() {
131
+ if (!this.config.token)
132
+ throw new Error('Not signed in. Run: omnitype login');
133
+ const res = await fetch(`${this.apiUrl}/provenance/personal/projects`, {
134
+ headers: { 'Authorization': `Bearer ${this.config.token}` },
135
+ });
136
+ if (!res.ok)
137
+ throw new Error(`Failed to fetch stats: HTTP ${res.status}`);
138
+ return res.json();
139
+ }
140
+ async getProfile() {
141
+ if (!this.config.token)
142
+ throw new Error('Not signed in. Run: omnitype login');
143
+ const res = await fetch(`${this.apiUrl}/auth/me`, {
144
+ headers: { 'Authorization': `Bearer ${this.config.token}` },
145
+ });
146
+ if (!res.ok)
147
+ throw new Error(`Failed to fetch profile: HTTP ${res.status}`);
148
+ return res.json();
149
+ }
150
+ logout() {
151
+ delete this.config.token;
152
+ delete this.config.username;
153
+ this._saveConfig();
154
+ }
155
+ async pushProvenance(projectName, branch, files, opts = {}) {
156
+ if (!this.config.token)
157
+ throw new Error('Not signed in. Run: omnitype login');
158
+ const fileEntries = Object.entries(files);
159
+ if (fileEntries.length === 0 && !opts.deletedFiles?.length)
160
+ return;
161
+ const MAX_CHUNK = 400 * 1024;
162
+ const chunks = this._buildChunks(fileEntries, MAX_CHUNK);
163
+ const total = chunks.length || 1;
164
+ const effective = chunks.length > 0 ? chunks : [{}];
165
+ const sem = new Semaphore(4);
166
+ const failed = [];
167
+ await Promise.all(effective.map(async (chunk, i) => {
168
+ await sem.acquire();
169
+ try {
170
+ const body = {
171
+ project_name: projectName, branch, files: chunk,
172
+ source: opts.source ?? 'cli-daemon',
173
+ };
174
+ if (i === 0) {
175
+ if (opts.commitHash)
176
+ body['commit_hash'] = opts.commitHash;
177
+ if (opts.commitMessage)
178
+ body['commit_message'] = opts.commitMessage;
179
+ if (opts.deletedFiles?.length)
180
+ body['deleted_files'] = opts.deletedFiles;
181
+ if (opts.personalOnly)
182
+ body['personal_only'] = true;
183
+ }
184
+ await this._postWithRetry(`${this.apiUrl}/provenance`, body, 3, `chunk ${i + 1}/${total}`);
185
+ }
186
+ catch {
187
+ failed.push(i + 1);
188
+ }
189
+ finally {
190
+ sem.release();
191
+ }
192
+ }));
193
+ if (failed.length)
194
+ throw new Error(`Push failed for chunks: ${failed.join(', ')}`);
195
+ }
196
+ async _postWithRetry(url, body, attempts, label) {
197
+ const compressed = await _gzip(Buffer.from(JSON.stringify(body)));
198
+ for (let i = 0; i < attempts; i++) {
199
+ const res = await fetch(url, {
200
+ method: 'POST',
201
+ headers: {
202
+ 'Authorization': `Bearer ${this.config.token}`,
203
+ 'Content-Type': 'application/json',
204
+ 'Content-Encoding': 'gzip',
205
+ },
206
+ body: compressed,
207
+ });
208
+ if (res.ok)
209
+ return;
210
+ if (res.status < 500 || i === attempts - 1)
211
+ throw new Error(`${label}: HTTP ${res.status}`);
212
+ await new Promise(r => setTimeout(r, 500 * 2 ** i));
213
+ }
214
+ }
215
+ _buildChunks(entries, maxBytes) {
216
+ const chunks = [];
217
+ let cur = {};
218
+ let curBytes = 0;
219
+ for (const [k, v] of entries) {
220
+ const size = JSON.stringify(v).length;
221
+ if (curBytes + size > maxBytes && curBytes > 0) {
222
+ chunks.push(cur);
223
+ cur = {};
224
+ curBytes = 0;
225
+ }
226
+ cur[k] = v;
227
+ curBytes += size;
228
+ }
229
+ if (Object.keys(cur).length)
230
+ chunks.push(cur);
231
+ return chunks;
232
+ }
233
+ }
234
+ exports.ApiClient = ApiClient;