githits 0.1.0 → 0.1.2
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/cli.js +118 -19
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-j0vey5g3.js → chunk-2wbvwsc9.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
version
|
|
4
|
-
} from "./shared/chunk-
|
|
4
|
+
} from "./shared/chunk-2wbvwsc9.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -424,6 +424,117 @@ class BrowserServiceImpl {
|
|
|
424
424
|
await open(url);
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
|
+
// src/services/chunking-keyring-service.ts
|
|
428
|
+
var WINDOWS_MAX_ENTRY_SIZE = 1200;
|
|
429
|
+
var CHUNKED_PREFIX = "CHUNKED:";
|
|
430
|
+
var MAX_CHUNK_COUNT = 100;
|
|
431
|
+
function chunkKey(account, writeId, index) {
|
|
432
|
+
return `${account}:chunk:${writeId}:${index}`;
|
|
433
|
+
}
|
|
434
|
+
function parseChunkedSentinel(value) {
|
|
435
|
+
if (!value.startsWith(CHUNKED_PREFIX))
|
|
436
|
+
return null;
|
|
437
|
+
const rest = value.slice(CHUNKED_PREFIX.length);
|
|
438
|
+
const colonIndex = rest.indexOf(":");
|
|
439
|
+
if (colonIndex === -1)
|
|
440
|
+
return null;
|
|
441
|
+
const writeId = rest.slice(0, colonIndex);
|
|
442
|
+
if (writeId.length === 0)
|
|
443
|
+
return null;
|
|
444
|
+
const countStr = rest.slice(colonIndex + 1);
|
|
445
|
+
const count = Number(countStr);
|
|
446
|
+
if (!Number.isInteger(count) || count <= 0)
|
|
447
|
+
return null;
|
|
448
|
+
return { writeId, count };
|
|
449
|
+
}
|
|
450
|
+
function splitIntoChunks(value, maxSize) {
|
|
451
|
+
if (value.length === 0)
|
|
452
|
+
return [""];
|
|
453
|
+
const chunks = [];
|
|
454
|
+
for (let offset = 0;offset < value.length; offset += maxSize) {
|
|
455
|
+
chunks.push(value.slice(offset, offset + maxSize));
|
|
456
|
+
}
|
|
457
|
+
return chunks;
|
|
458
|
+
}
|
|
459
|
+
function generateWriteId() {
|
|
460
|
+
let id;
|
|
461
|
+
do {
|
|
462
|
+
id = Math.random().toString(36).slice(2, 8);
|
|
463
|
+
} while (id.length < 6);
|
|
464
|
+
return id;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
class ChunkingKeyringService {
|
|
468
|
+
inner;
|
|
469
|
+
maxEntrySize;
|
|
470
|
+
constructor(inner, maxEntrySize = WINDOWS_MAX_ENTRY_SIZE) {
|
|
471
|
+
this.inner = inner;
|
|
472
|
+
this.maxEntrySize = maxEntrySize;
|
|
473
|
+
}
|
|
474
|
+
getPassword(service, account) {
|
|
475
|
+
const value = this.inner.getPassword(service, account);
|
|
476
|
+
if (value === null)
|
|
477
|
+
return null;
|
|
478
|
+
if (!value.startsWith(CHUNKED_PREFIX))
|
|
479
|
+
return value;
|
|
480
|
+
const sentinel = parseChunkedSentinel(value);
|
|
481
|
+
if (sentinel === null)
|
|
482
|
+
return null;
|
|
483
|
+
const chunks = [];
|
|
484
|
+
for (let i = 0;i < sentinel.count; i++) {
|
|
485
|
+
const chunk = this.inner.getPassword(service, chunkKey(account, sentinel.writeId, i));
|
|
486
|
+
if (chunk === null) {
|
|
487
|
+
console.error(`Warning: Incomplete chunked keychain entry for "${account}" (missing chunk ${i} of ${sentinel.count}). Treating as missing.`);
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
chunks.push(chunk);
|
|
491
|
+
}
|
|
492
|
+
return chunks.join("");
|
|
493
|
+
}
|
|
494
|
+
setPassword(service, account, password) {
|
|
495
|
+
const oldValue = this.readOldSentinel(service, account);
|
|
496
|
+
if (password.length <= this.maxEntrySize) {
|
|
497
|
+
this.inner.setPassword(service, account, password);
|
|
498
|
+
} else {
|
|
499
|
+
const chunks = splitIntoChunks(password, this.maxEntrySize);
|
|
500
|
+
if (chunks.length > MAX_CHUNK_COUNT) {
|
|
501
|
+
throw new Error(`Value requires ${chunks.length} chunks, exceeding maximum of ${MAX_CHUNK_COUNT}. ` + `This likely indicates a bug — credential data should not be this large.`);
|
|
502
|
+
}
|
|
503
|
+
const writeId = generateWriteId();
|
|
504
|
+
for (const [i, chunk] of chunks.entries()) {
|
|
505
|
+
this.inner.setPassword(service, chunkKey(account, writeId, i), chunk);
|
|
506
|
+
}
|
|
507
|
+
this.inner.setPassword(service, account, `${CHUNKED_PREFIX}${writeId}:${chunks.length}`);
|
|
508
|
+
}
|
|
509
|
+
if (oldValue !== null) {
|
|
510
|
+
this.deleteChunkEntries(service, account, oldValue);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
deletePassword(service, account) {
|
|
514
|
+
const oldValue = this.readOldSentinel(service, account);
|
|
515
|
+
if (oldValue !== null) {
|
|
516
|
+
this.deleteChunkEntries(service, account, oldValue);
|
|
517
|
+
}
|
|
518
|
+
return this.inner.deletePassword(service, account);
|
|
519
|
+
}
|
|
520
|
+
readOldSentinel(service, account) {
|
|
521
|
+
try {
|
|
522
|
+
const value = this.inner.getPassword(service, account);
|
|
523
|
+
if (value === null)
|
|
524
|
+
return null;
|
|
525
|
+
return parseChunkedSentinel(value);
|
|
526
|
+
} catch {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
deleteChunkEntries(service, account, sentinel) {
|
|
531
|
+
for (let i = 0;i < sentinel.count; i++) {
|
|
532
|
+
try {
|
|
533
|
+
this.inner.deletePassword(service, chunkKey(account, sentinel.writeId, i));
|
|
534
|
+
} catch {}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
427
538
|
// src/services/config.ts
|
|
428
539
|
var DEFAULT_MCP_URL = "https://mcp.githits.com";
|
|
429
540
|
var DEFAULT_API_URL = "https://api.githits.com";
|
|
@@ -928,7 +1039,8 @@ class TokenManager {
|
|
|
928
1039
|
function createAuthStorage(fileSystemService) {
|
|
929
1040
|
const fileStorage = new AuthStorageImpl(fileSystemService);
|
|
930
1041
|
try {
|
|
931
|
-
const
|
|
1042
|
+
const rawKeyring = new KeyringServiceImpl;
|
|
1043
|
+
const keyring = process.platform === "win32" ? new ChunkingKeyringService(rawKeyring, WINDOWS_MAX_ENTRY_SIZE) : rawKeyring;
|
|
932
1044
|
const probeKey = `__probe_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
933
1045
|
keyring.setPassword("githits", probeKey, "probe");
|
|
934
1046
|
try {
|
|
@@ -1597,7 +1709,6 @@ function createMcpServer(deps) {
|
|
|
1597
1709
|
return server;
|
|
1598
1710
|
}
|
|
1599
1711
|
async function startMcpServer(deps) {
|
|
1600
|
-
requireAuth(deps, "to start MCP server");
|
|
1601
1712
|
const server = createMcpServer(deps);
|
|
1602
1713
|
const transport = new StdioServerTransport;
|
|
1603
1714
|
await server.connect(transport);
|
|
@@ -1629,27 +1740,15 @@ Available tools: search, search_language, feedback`).action(async () => {
|
|
|
1629
1740
|
showMcpSetupInstructions();
|
|
1630
1741
|
return;
|
|
1631
1742
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
await startMcpServer(deps);
|
|
1635
|
-
} catch (error) {
|
|
1636
|
-
if (error instanceof AuthRequiredError)
|
|
1637
|
-
process.exit(1);
|
|
1638
|
-
throw error;
|
|
1639
|
-
}
|
|
1743
|
+
const deps = await createContainer();
|
|
1744
|
+
await startMcpServer(deps);
|
|
1640
1745
|
});
|
|
1641
1746
|
mcpCommand.command("start").summary("Start MCP server (stdio mode)").description(`Start the MCP server using STDIO transport.
|
|
1642
1747
|
|
|
1643
1748
|
This command explicitly starts the server and is intended for use
|
|
1644
1749
|
in MCP configuration files. Use 'githits mcp' for interactive setup.`).action(async () => {
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
await startMcpServer(deps);
|
|
1648
|
-
} catch (error) {
|
|
1649
|
-
if (error instanceof AuthRequiredError)
|
|
1650
|
-
process.exit(1);
|
|
1651
|
-
throw error;
|
|
1652
|
-
}
|
|
1750
|
+
const deps = await createContainer();
|
|
1751
|
+
await startMcpServer(deps);
|
|
1653
1752
|
});
|
|
1654
1753
|
}
|
|
1655
1754
|
// src/commands/search.ts
|
package/dist/index.js
CHANGED