@brianli/kimaki 0.4.73-brianli.1 → 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 +77 -58
- package/package.json +4 -4
- package/src/cli.ts +97 -73
package/dist/cli.js
CHANGED
|
@@ -43,6 +43,8 @@ const KIMAKI_GATEWAY_PROXY_URL = process.env.KIMAKI_GATEWAY_PROXY_URL ||
|
|
|
43
43
|
const KIMAKI_GATEWAY_PROXY_REST_BASE_URL = getGatewayProxyRestBaseUrl({
|
|
44
44
|
gatewayUrl: KIMAKI_GATEWAY_PROXY_URL,
|
|
45
45
|
});
|
|
46
|
+
const KIMAKI_CLIENT_ID = process.env.KIMAKI_CLIENT_ID?.trim() || undefined;
|
|
47
|
+
const KIMAKI_CLIENT_SECRET = process.env.KIMAKI_CLIENT_SECRET?.trim() || undefined;
|
|
46
48
|
// Strip bracketed paste escape sequences from terminal input.
|
|
47
49
|
// iTerm2 and other terminals wrap pasted content with \x1b[200~ and \x1b[201~
|
|
48
50
|
// which can cause validation to fail on macOS. See: https://github.com/remorses/kimaki/issues/18
|
|
@@ -903,70 +905,82 @@ async function run({ restartOnboarding, addChannels, useWorktrees, enableVoiceCh
|
|
|
903
905
|
cliLogger.error('Gateway mode is not available yet. KIMAKI_SHARED_APP_ID is not configured.');
|
|
904
906
|
process.exit(EXIT_NO_RESTART);
|
|
905
907
|
}
|
|
906
|
-
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
:
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
// 600 attempts x 2s = 20 minutes timeout.
|
|
927
|
-
const s = spinner();
|
|
928
|
-
s.start('Waiting for a Discord server with the bot installed...');
|
|
929
|
-
const pollUrl = new URL('/api/onboarding/status', KIMAKI_WEBSITE_URL);
|
|
930
|
-
pollUrl.searchParams.set('client_id', clientId);
|
|
931
|
-
pollUrl.searchParams.set('secret', clientSecret);
|
|
932
|
-
let guildId;
|
|
933
|
-
for (let attempt = 0; attempt < 600; attempt++) {
|
|
934
|
-
await new Promise((resolve) => {
|
|
935
|
-
setTimeout(resolve, 2000);
|
|
908
|
+
const hasGatewayClientId = Boolean(KIMAKI_CLIENT_ID);
|
|
909
|
+
const hasGatewayClientSecret = Boolean(KIMAKI_CLIENT_SECRET);
|
|
910
|
+
if (hasGatewayClientId !== hasGatewayClientSecret) {
|
|
911
|
+
cliLogger.error('Gateway credential env vars must be set together: KIMAKI_CLIENT_ID and KIMAKI_CLIENT_SECRET.');
|
|
912
|
+
process.exit(EXIT_NO_RESTART);
|
|
913
|
+
}
|
|
914
|
+
const clientId = KIMAKI_CLIENT_ID || crypto.randomUUID();
|
|
915
|
+
const clientSecret = KIMAKI_CLIENT_SECRET
|
|
916
|
+
|| crypto.randomBytes(32).toString('hex');
|
|
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',
|
|
936
928
|
});
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
+
}
|
|
957
971
|
}
|
|
958
972
|
}
|
|
973
|
+
catch {
|
|
974
|
+
// Network error, retry
|
|
975
|
+
}
|
|
959
976
|
}
|
|
960
|
-
|
|
961
|
-
|
|
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);
|
|
962
981
|
}
|
|
982
|
+
s.stop('Bot authorized successfully!');
|
|
963
983
|
}
|
|
964
|
-
if (!guildId) {
|
|
965
|
-
s.stop('Authorization timed out');
|
|
966
|
-
cliLogger.error('Bot authorization timed out after 20 minutes. Please try again.');
|
|
967
|
-
process.exit(EXIT_NO_RESTART);
|
|
968
|
-
}
|
|
969
|
-
s.stop('Bot authorized successfully!');
|
|
970
984
|
await setBotMode({
|
|
971
985
|
appId: KIMAKI_SHARED_APP_ID,
|
|
972
986
|
mode: 'gateway',
|
|
@@ -1056,6 +1070,11 @@ async function run({ restartOnboarding, addChannels, useWorktrees, enableVoiceCh
|
|
|
1056
1070
|
// ensures they always get the bot that's actually running.
|
|
1057
1071
|
await touchBotTokenTimestamp(appId);
|
|
1058
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
|
+
});
|
|
1059
1078
|
// Start OpenCode server EARLY - let it initialize in parallel with Discord login.
|
|
1060
1079
|
// This is the biggest startup bottleneck (can take 1-30 seconds to spawn and wait for ready)
|
|
1061
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
|
@@ -133,6 +133,8 @@ const KIMAKI_GATEWAY_PROXY_URL =
|
|
|
133
133
|
const KIMAKI_GATEWAY_PROXY_REST_BASE_URL = getGatewayProxyRestBaseUrl({
|
|
134
134
|
gatewayUrl: KIMAKI_GATEWAY_PROXY_URL,
|
|
135
135
|
})
|
|
136
|
+
const KIMAKI_CLIENT_ID = process.env.KIMAKI_CLIENT_ID?.trim() || undefined
|
|
137
|
+
const KIMAKI_CLIENT_SECRET = process.env.KIMAKI_CLIENT_SECRET?.trim() || undefined
|
|
136
138
|
|
|
137
139
|
// Strip bracketed paste escape sequences from terminal input.
|
|
138
140
|
// iTerm2 and other terminals wrap pasted content with \x1b[200~ and \x1b[201~
|
|
@@ -1303,89 +1305,105 @@ async function run({
|
|
|
1303
1305
|
process.exit(EXIT_NO_RESTART)
|
|
1304
1306
|
}
|
|
1305
1307
|
|
|
1306
|
-
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1308
|
+
const hasGatewayClientId = Boolean(KIMAKI_CLIENT_ID)
|
|
1309
|
+
const hasGatewayClientSecret = Boolean(KIMAKI_CLIENT_SECRET)
|
|
1310
|
+
if (hasGatewayClientId !== hasGatewayClientSecret) {
|
|
1311
|
+
cliLogger.error(
|
|
1312
|
+
'Gateway credential env vars must be set together: KIMAKI_CLIENT_ID and KIMAKI_CLIENT_SECRET.',
|
|
1313
|
+
)
|
|
1314
|
+
process.exit(EXIT_NO_RESTART)
|
|
1315
|
+
}
|
|
1316
|
+
const clientId = KIMAKI_CLIENT_ID || crypto.randomUUID()
|
|
1317
|
+
const clientSecret = KIMAKI_CLIENT_SECRET
|
|
1318
|
+
|| crypto.randomBytes(32).toString('hex')
|
|
1319
|
+
const hasGatewayEnvCredentials = hasGatewayClientId && hasGatewayClientSecret
|
|
1320
|
+
if (hasGatewayEnvCredentials) {
|
|
1321
|
+
cliLogger.log(
|
|
1322
|
+
'Using KIMAKI_CLIENT_ID and KIMAKI_CLIENT_SECRET from environment for gateway mode. Skipping OAuth onboarding.',
|
|
1323
|
+
)
|
|
1324
|
+
}
|
|
1309
1325
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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
|
+
})
|
|
1317
1334
|
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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
|
+
)
|
|
1322
1339
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
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
|
+
})
|
|
1347
1364
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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
|
+
}
|
|
1365
1382
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
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
|
+
}
|
|
1373
1391
|
}
|
|
1392
|
+
} catch {
|
|
1393
|
+
// Network error, retry
|
|
1374
1394
|
}
|
|
1375
|
-
} catch {
|
|
1376
|
-
// Network error, retry
|
|
1377
1395
|
}
|
|
1378
|
-
}
|
|
1379
1396
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
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
|
+
}
|
|
1387
1404
|
|
|
1388
|
-
|
|
1405
|
+
s.stop('Bot authorized successfully!')
|
|
1406
|
+
}
|
|
1389
1407
|
|
|
1390
1408
|
await setBotMode({
|
|
1391
1409
|
appId: KIMAKI_SHARED_APP_ID,
|
|
@@ -1505,6 +1523,12 @@ async function run({
|
|
|
1505
1523
|
const shouldAddChannels =
|
|
1506
1524
|
!isQuickStart || forceRestartOnboarding || Boolean(addChannels)
|
|
1507
1525
|
|
|
1526
|
+
store.setState({
|
|
1527
|
+
discordBaseUrl: isGatewayMode
|
|
1528
|
+
? KIMAKI_GATEWAY_PROXY_REST_BASE_URL
|
|
1529
|
+
: 'https://discord.com',
|
|
1530
|
+
})
|
|
1531
|
+
|
|
1508
1532
|
// Start OpenCode server EARLY - let it initialize in parallel with Discord login.
|
|
1509
1533
|
// This is the biggest startup bottleneck (can take 1-30 seconds to spawn and wait for ready)
|
|
1510
1534
|
const currentDir = process.cwd()
|