@brianli/kimaki 0.4.73-brianli.2 → 0.4.73-brianli.3
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 +66 -58
- package/package.json +4 -4
- package/src/cli.ts +81 -72
package/dist/cli.js
CHANGED
|
@@ -914,70 +914,73 @@ async function run({ restartOnboarding, addChannels, useWorktrees, enableVoiceCh
|
|
|
914
914
|
const clientId = KIMAKI_CLIENT_ID || crypto.randomUUID();
|
|
915
915
|
const clientSecret = KIMAKI_CLIENT_SECRET
|
|
916
916
|
|| crypto.randomBytes(32).toString('hex');
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
clientId
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
// Open URL in default browser
|
|
929
|
-
const { exec } = await import('node:child_process');
|
|
930
|
-
const openCmd = process.platform === 'darwin'
|
|
931
|
-
? 'open'
|
|
932
|
-
: process.platform === 'win32'
|
|
933
|
-
? 'start'
|
|
934
|
-
: 'xdg-open';
|
|
935
|
-
exec(`${openCmd} "${oauthUrl}"`);
|
|
936
|
-
// Poll until the user installs the bot in a Discord server.
|
|
937
|
-
// 600 attempts x 2s = 20 minutes timeout.
|
|
938
|
-
const s = spinner();
|
|
939
|
-
s.start('Waiting for a Discord server with the bot installed...');
|
|
940
|
-
const pollUrl = new URL('/api/onboarding/status', KIMAKI_WEBSITE_URL);
|
|
941
|
-
pollUrl.searchParams.set('client_id', clientId);
|
|
942
|
-
pollUrl.searchParams.set('secret', clientSecret);
|
|
943
|
-
let guildId;
|
|
944
|
-
for (let attempt = 0; attempt < 600; attempt++) {
|
|
945
|
-
await new Promise((resolve) => {
|
|
946
|
-
setTimeout(resolve, 2000);
|
|
917
|
+
const hasGatewayEnvCredentials = hasGatewayClientId && hasGatewayClientSecret;
|
|
918
|
+
if (hasGatewayEnvCredentials) {
|
|
919
|
+
cliLogger.log('Using KIMAKI_CLIENT_ID and KIMAKI_CLIENT_SECRET from environment for gateway mode. Skipping OAuth onboarding.');
|
|
920
|
+
}
|
|
921
|
+
if (!hasGatewayEnvCredentials) {
|
|
922
|
+
const statePayload = JSON.stringify({ clientId, clientSecret });
|
|
923
|
+
const oauthUrl = generateBotInstallUrl({
|
|
924
|
+
clientId: KIMAKI_SHARED_APP_ID,
|
|
925
|
+
state: statePayload,
|
|
926
|
+
redirectUri: `${KIMAKI_WEBSITE_URL}/api/auth/callback/discord`,
|
|
927
|
+
responseType: 'code',
|
|
947
928
|
});
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
929
|
+
note(`Open this URL to install the Kimaki bot in your Discord server:\n\n${oauthUrl}\n\nDo not share this URL with anyone — it contains your credentials.\n\nIf you don't have a server, create one first (+ button in the Discord sidebar).`, 'Install Bot');
|
|
930
|
+
// Open URL in default browser
|
|
931
|
+
const { exec } = await import('node:child_process');
|
|
932
|
+
const openCmd = process.platform === 'darwin'
|
|
933
|
+
? 'open'
|
|
934
|
+
: process.platform === 'win32'
|
|
935
|
+
? 'start'
|
|
936
|
+
: 'xdg-open';
|
|
937
|
+
exec(`${openCmd} "${oauthUrl}"`);
|
|
938
|
+
// Poll until the user installs the bot in a Discord server.
|
|
939
|
+
// 600 attempts x 2s = 20 minutes timeout.
|
|
940
|
+
const s = spinner();
|
|
941
|
+
s.start('Waiting for a Discord server with the bot installed...');
|
|
942
|
+
const pollUrl = new URL('/api/onboarding/status', KIMAKI_WEBSITE_URL);
|
|
943
|
+
pollUrl.searchParams.set('client_id', clientId);
|
|
944
|
+
pollUrl.searchParams.set('secret', clientSecret);
|
|
945
|
+
let guildId;
|
|
946
|
+
for (let attempt = 0; attempt < 600; attempt++) {
|
|
947
|
+
await new Promise((resolve) => {
|
|
948
|
+
setTimeout(resolve, 2000);
|
|
949
|
+
});
|
|
950
|
+
// Progressive hints for users who may be stuck
|
|
951
|
+
if (attempt === 15) {
|
|
952
|
+
// ~30s
|
|
953
|
+
s.message('Still waiting... Select a server in the Discord authorization page and click "Authorize"');
|
|
954
|
+
}
|
|
955
|
+
else if (attempt === 45) {
|
|
956
|
+
// ~90s
|
|
957
|
+
s.message(`Still waiting... If you don't see any servers, create one first (+ button in Discord sidebar), then reopen the URL above`);
|
|
958
|
+
}
|
|
959
|
+
else if (attempt === 150) {
|
|
960
|
+
// ~5min
|
|
961
|
+
s.message(`Still waiting... Reopen the install URL if you closed it:\n${oauthUrl}`);
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
const resp = await fetch(pollUrl.toString());
|
|
965
|
+
if (resp.ok) {
|
|
966
|
+
const data = (await resp.json());
|
|
967
|
+
if (data.guild_id) {
|
|
968
|
+
guildId = data.guild_id;
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
968
971
|
}
|
|
969
972
|
}
|
|
973
|
+
catch {
|
|
974
|
+
// Network error, retry
|
|
975
|
+
}
|
|
970
976
|
}
|
|
971
|
-
|
|
972
|
-
|
|
977
|
+
if (!guildId) {
|
|
978
|
+
s.stop('Authorization timed out');
|
|
979
|
+
cliLogger.error('Bot authorization timed out after 20 minutes. Please try again.');
|
|
980
|
+
process.exit(EXIT_NO_RESTART);
|
|
973
981
|
}
|
|
982
|
+
s.stop('Bot authorized successfully!');
|
|
974
983
|
}
|
|
975
|
-
if (!guildId) {
|
|
976
|
-
s.stop('Authorization timed out');
|
|
977
|
-
cliLogger.error('Bot authorization timed out after 20 minutes. Please try again.');
|
|
978
|
-
process.exit(EXIT_NO_RESTART);
|
|
979
|
-
}
|
|
980
|
-
s.stop('Bot authorized successfully!');
|
|
981
984
|
await setBotMode({
|
|
982
985
|
appId: KIMAKI_SHARED_APP_ID,
|
|
983
986
|
mode: 'gateway',
|
|
@@ -1067,6 +1070,11 @@ async function run({ restartOnboarding, addChannels, useWorktrees, enableVoiceCh
|
|
|
1067
1070
|
// ensures they always get the bot that's actually running.
|
|
1068
1071
|
await touchBotTokenTimestamp(appId);
|
|
1069
1072
|
const shouldAddChannels = !isQuickStart || forceRestartOnboarding || Boolean(addChannels);
|
|
1073
|
+
store.setState({
|
|
1074
|
+
discordBaseUrl: isGatewayMode
|
|
1075
|
+
? KIMAKI_GATEWAY_PROXY_REST_BASE_URL
|
|
1076
|
+
: 'https://discord.com',
|
|
1077
|
+
});
|
|
1070
1078
|
// Start OpenCode server EARLY - let it initialize in parallel with Discord login.
|
|
1071
1079
|
// This is the biggest startup bottleneck (can take 1-30 seconds to spawn and wait for ready)
|
|
1072
1080
|
const currentDir = process.cwd();
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@brianli/kimaki",
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.4.73-brianli.
|
|
5
|
+
"version": "0.4.73-brianli.3",
|
|
6
6
|
"repository": "https://github.com/remorses/kimaki",
|
|
7
7
|
"bin": "bin.js",
|
|
8
8
|
"files": [
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"prisma": "7.3.0",
|
|
23
23
|
"tsx": "^4.20.5",
|
|
24
24
|
"discord-digital-twin": "^0.0.1",
|
|
25
|
-
"db": "^0.0.0",
|
|
26
25
|
"opencode-cached-provider": "^0.0.1",
|
|
26
|
+
"db": "^0.0.0",
|
|
27
27
|
"opencode-deterministic-provider": "^0.0.1"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"xdg-basedir": "^5.1.0",
|
|
58
58
|
"zod": "^4.3.6",
|
|
59
59
|
"zustand": "^5.0.11",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
60
|
+
"traforo": "^0.0.9",
|
|
61
|
+
"errore": "^0.14.0"
|
|
62
62
|
},
|
|
63
63
|
"optionalDependencies": {
|
|
64
64
|
"@discordjs/opus": "^0.10.0",
|
package/src/cli.ts
CHANGED
|
@@ -1316,91 +1316,94 @@ async function run({
|
|
|
1316
1316
|
const clientId = KIMAKI_CLIENT_ID || crypto.randomUUID()
|
|
1317
1317
|
const clientSecret = KIMAKI_CLIENT_SECRET
|
|
1318
1318
|
|| crypto.randomBytes(32).toString('hex')
|
|
1319
|
-
|
|
1319
|
+
const hasGatewayEnvCredentials = hasGatewayClientId && hasGatewayClientSecret
|
|
1320
|
+
if (hasGatewayEnvCredentials) {
|
|
1320
1321
|
cliLogger.log(
|
|
1321
|
-
'Using KIMAKI_CLIENT_ID and KIMAKI_CLIENT_SECRET from environment for gateway mode.',
|
|
1322
|
+
'Using KIMAKI_CLIENT_ID and KIMAKI_CLIENT_SECRET from environment for gateway mode. Skipping OAuth onboarding.',
|
|
1322
1323
|
)
|
|
1323
1324
|
}
|
|
1324
1325
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1326
|
+
if (!hasGatewayEnvCredentials) {
|
|
1327
|
+
const statePayload = JSON.stringify({ clientId, clientSecret } satisfies GatewayOAuthState)
|
|
1328
|
+
const oauthUrl = generateBotInstallUrl({
|
|
1329
|
+
clientId: KIMAKI_SHARED_APP_ID,
|
|
1330
|
+
state: statePayload,
|
|
1331
|
+
redirectUri: `${KIMAKI_WEBSITE_URL}/api/auth/callback/discord`,
|
|
1332
|
+
responseType: 'code',
|
|
1333
|
+
})
|
|
1332
1334
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1335
|
+
note(
|
|
1336
|
+
`Open this URL to install the Kimaki bot in your Discord server:\n\n${oauthUrl}\n\nDo not share this URL with anyone — it contains your credentials.\n\nIf you don't have a server, create one first (+ button in the Discord sidebar).`,
|
|
1337
|
+
'Install Bot',
|
|
1338
|
+
)
|
|
1337
1339
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1340
|
+
// Open URL in default browser
|
|
1341
|
+
const { exec } = await import('node:child_process')
|
|
1342
|
+
const openCmd =
|
|
1343
|
+
process.platform === 'darwin'
|
|
1344
|
+
? 'open'
|
|
1345
|
+
: process.platform === 'win32'
|
|
1346
|
+
? 'start'
|
|
1347
|
+
: 'xdg-open'
|
|
1348
|
+
exec(`${openCmd} "${oauthUrl}"`)
|
|
1349
|
+
|
|
1350
|
+
// Poll until the user installs the bot in a Discord server.
|
|
1351
|
+
// 600 attempts x 2s = 20 minutes timeout.
|
|
1352
|
+
const s = spinner()
|
|
1353
|
+
s.start('Waiting for a Discord server with the bot installed...')
|
|
1354
|
+
|
|
1355
|
+
const pollUrl = new URL('/api/onboarding/status', KIMAKI_WEBSITE_URL)
|
|
1356
|
+
pollUrl.searchParams.set('client_id', clientId)
|
|
1357
|
+
pollUrl.searchParams.set('secret', clientSecret)
|
|
1358
|
+
|
|
1359
|
+
let guildId: string | undefined
|
|
1360
|
+
for (let attempt = 0; attempt < 600; attempt++) {
|
|
1361
|
+
await new Promise((resolve) => {
|
|
1362
|
+
setTimeout(resolve, 2000)
|
|
1363
|
+
})
|
|
1362
1364
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1365
|
+
// Progressive hints for users who may be stuck
|
|
1366
|
+
if (attempt === 15) {
|
|
1367
|
+
// ~30s
|
|
1368
|
+
s.message(
|
|
1369
|
+
'Still waiting... Select a server in the Discord authorization page and click "Authorize"',
|
|
1370
|
+
)
|
|
1371
|
+
} else if (attempt === 45) {
|
|
1372
|
+
// ~90s
|
|
1373
|
+
s.message(
|
|
1374
|
+
`Still waiting... If you don't see any servers, create one first (+ button in Discord sidebar), then reopen the URL above`,
|
|
1375
|
+
)
|
|
1376
|
+
} else if (attempt === 150) {
|
|
1377
|
+
// ~5min
|
|
1378
|
+
s.message(
|
|
1379
|
+
`Still waiting... Reopen the install URL if you closed it:\n${oauthUrl}`,
|
|
1380
|
+
)
|
|
1381
|
+
}
|
|
1380
1382
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1383
|
+
try {
|
|
1384
|
+
const resp = await fetch(pollUrl.toString())
|
|
1385
|
+
if (resp.ok) {
|
|
1386
|
+
const data = (await resp.json()) as { guild_id?: string }
|
|
1387
|
+
if (data.guild_id) {
|
|
1388
|
+
guildId = data.guild_id
|
|
1389
|
+
break
|
|
1390
|
+
}
|
|
1388
1391
|
}
|
|
1392
|
+
} catch {
|
|
1393
|
+
// Network error, retry
|
|
1389
1394
|
}
|
|
1390
|
-
} catch {
|
|
1391
|
-
// Network error, retry
|
|
1392
1395
|
}
|
|
1393
|
-
}
|
|
1394
1396
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1397
|
+
if (!guildId) {
|
|
1398
|
+
s.stop('Authorization timed out')
|
|
1399
|
+
cliLogger.error(
|
|
1400
|
+
'Bot authorization timed out after 20 minutes. Please try again.',
|
|
1401
|
+
)
|
|
1402
|
+
process.exit(EXIT_NO_RESTART)
|
|
1403
|
+
}
|
|
1402
1404
|
|
|
1403
|
-
|
|
1405
|
+
s.stop('Bot authorized successfully!')
|
|
1406
|
+
}
|
|
1404
1407
|
|
|
1405
1408
|
await setBotMode({
|
|
1406
1409
|
appId: KIMAKI_SHARED_APP_ID,
|
|
@@ -1520,6 +1523,12 @@ async function run({
|
|
|
1520
1523
|
const shouldAddChannels =
|
|
1521
1524
|
!isQuickStart || forceRestartOnboarding || Boolean(addChannels)
|
|
1522
1525
|
|
|
1526
|
+
store.setState({
|
|
1527
|
+
discordBaseUrl: isGatewayMode
|
|
1528
|
+
? KIMAKI_GATEWAY_PROXY_REST_BASE_URL
|
|
1529
|
+
: 'https://discord.com',
|
|
1530
|
+
})
|
|
1531
|
+
|
|
1523
1532
|
// Start OpenCode server EARLY - let it initialize in parallel with Discord login.
|
|
1524
1533
|
// This is the biggest startup bottleneck (can take 1-30 seconds to spawn and wait for ready)
|
|
1525
1534
|
const currentDir = process.cwd()
|