From c473823a4bee440340b5b7efb4f632ccccf64f84 Mon Sep 17 00:00:00 2001 From: 7Sageer <12210216@mail.sustech.edu.cn> Date: Tue, 26 May 2026 14:24:42 +0800 Subject: [PATCH] feat(kimi-code): use positional args and bare keywords for /connect (#50) Replace the --flag style (/connect --url=..., /connect --refresh) with positional arguments and bare keywords: - /connect https://example.com/catalog.json - /connect refresh - /connect (no args, default behavior) This aligns the slash-command UX with peer CLI tools (claude-code, pi, opencode) where slash commands use bare words rather than CLI-style flags. Closes #30 follow-up --- apps/kimi-code/src/tui/kimi-tui.ts | 2 +- .../src/tui/utils/connect-catalog.ts | 60 +++++++++---- .../test/tui/utils/connect-catalog.test.ts | 88 ++++++++----------- 3 files changed, 82 insertions(+), 68 deletions(-) diff --git a/apps/kimi-code/src/tui/kimi-tui.ts b/apps/kimi-code/src/tui/kimi-tui.ts index 10fb186b0..08b1d8451 100644 --- a/apps/kimi-code/src/tui/kimi-tui.ts +++ b/apps/kimi-code/src/tui/kimi-tui.ts @@ -5224,7 +5224,7 @@ export class KimiTUI { if (preferBuiltIn) { const builtIn = loadBuiltInCatalog(BUILT_IN_CATALOG_JSON); if (builtIn !== undefined) { - this.showStatus('Loaded built-in catalog. Run /connect --refresh for the latest.'); + this.showStatus('Loaded built-in catalog. Run /connect refresh for the latest.'); catalog = builtIn; } } diff --git a/apps/kimi-code/src/tui/utils/connect-catalog.ts b/apps/kimi-code/src/tui/utils/connect-catalog.ts index 341ec93ec..dfd86bda5 100644 --- a/apps/kimi-code/src/tui/utils/connect-catalog.ts +++ b/apps/kimi-code/src/tui/utils/connect-catalog.ts @@ -1,8 +1,5 @@ import { DEFAULT_CATALOG_URL } from '@moonshot-ai/kimi-code-sdk'; -const CATALOG_URL_FLAG_RE = /--url(?:=|\s+)(https?:\/\/\S+)/; -const URL_FLAG_PRESENT_RE = /(?:^|\s)--url(?=\s|=|$)/; -const REFRESH_FLAG_RE = /(?:^|\s)--refresh(?=\s|$)/; const BARE_HTTP_URL_RE = /^https?:\/\/\S+$/; export interface ConnectCatalogRequest { @@ -17,9 +14,51 @@ export type ConnectCatalogResolution = export function resolveConnectCatalogRequest(args: string): ConnectCatalogResolution { const trimmed = args.trim(); - const urlMatch = CATALOG_URL_FLAG_RE.exec(trimmed); - const bareUrl = BARE_HTTP_URL_RE.test(trimmed) ? trimmed : undefined; - const explicitUrl = urlMatch?.[1] ?? bareUrl; + + if (trimmed === '') { + return { + kind: 'ok', + request: { + url: DEFAULT_CATALOG_URL, + preferBuiltIn: true, + allowBuiltInFallback: true, + }, + }; + } + + const tokens = trimmed.split(/\s+/).filter(Boolean); + let explicitUrl: string | undefined; + let refreshRequested = false; + + for (const token of tokens) { + if (token.toLowerCase() === 'refresh') { + refreshRequested = true; + continue; + } + + if (BARE_HTTP_URL_RE.test(token)) { + if (explicitUrl !== undefined) { + return { + kind: 'error', + message: `Only one catalog URL can be provided. Got "${explicitUrl}" and "${token}".`, + }; + } + explicitUrl = token; + continue; + } + + if (token.startsWith('--')) { + return { + kind: 'error', + message: `Unexpected flag "${token}". Use /connect [url] [refresh] instead.`, + }; + } + + return { + kind: 'error', + message: `Unknown argument "${token}". Usage: /connect [url] [refresh]`, + }; + } if (explicitUrl !== undefined) { return { @@ -32,15 +71,6 @@ export function resolveConnectCatalogRequest(args: string): ConnectCatalogResolu }; } - if (URL_FLAG_PRESENT_RE.test(trimmed)) { - return { - kind: 'error', - message: - '--url requires an http(s) URL value, e.g. /connect --url=https://example.com/catalog.json', - }; - } - - const refreshRequested = REFRESH_FLAG_RE.test(trimmed); return { kind: 'ok', request: { diff --git a/apps/kimi-code/test/tui/utils/connect-catalog.test.ts b/apps/kimi-code/test/tui/utils/connect-catalog.test.ts index 1de473601..98ece0d29 100644 --- a/apps/kimi-code/test/tui/utils/connect-catalog.test.ts +++ b/apps/kimi-code/test/tui/utils/connect-catalog.test.ts @@ -20,18 +20,10 @@ describe('resolveConnectCatalogRequest', () => { allowBuiltInFallback: true, }, }); - expect(resolveConnectCatalogRequest('ignored text')).toEqual({ - kind: 'ok', - request: { - url: DEFAULT_CATALOG_URL, - preferBuiltIn: true, - allowBuiltInFallback: true, - }, - }); }); - it('forces an online fetch when --refresh is requested', () => { - expect(resolveConnectCatalogRequest('--refresh')).toEqual({ + it('forces an online fetch when refresh is requested', () => { + expect(resolveConnectCatalogRequest('refresh')).toEqual({ kind: 'ok', request: { url: DEFAULT_CATALOG_URL, @@ -39,7 +31,7 @@ describe('resolveConnectCatalogRequest', () => { allowBuiltInFallback: true, }, }); - expect(resolveConnectCatalogRequest(' --refresh ')).toEqual({ + expect(resolveConnectCatalogRequest(' refresh ')).toEqual({ kind: 'ok', request: { url: DEFAULT_CATALOG_URL, @@ -49,23 +41,7 @@ describe('resolveConnectCatalogRequest', () => { }); }); - it('treats explicit catalog URLs as authoritative and ignores --refresh on them', () => { - expect(resolveConnectCatalogRequest('--url=https://internal.example/catalog.json')).toEqual({ - kind: 'ok', - request: { - url: 'https://internal.example/catalog.json', - preferBuiltIn: false, - allowBuiltInFallback: false, - }, - }); - expect(resolveConnectCatalogRequest('--url https://internal.example/catalog.json')).toEqual({ - kind: 'ok', - request: { - url: 'https://internal.example/catalog.json', - preferBuiltIn: false, - allowBuiltInFallback: false, - }, - }); + it('treats explicit catalog URLs as authoritative and ignores refresh on them', () => { expect(resolveConnectCatalogRequest('https://internal.example/catalog.json')).toEqual({ kind: 'ok', request: { @@ -75,7 +51,17 @@ describe('resolveConnectCatalogRequest', () => { }, }); expect( - resolveConnectCatalogRequest('--refresh --url=https://internal.example/catalog.json'), + resolveConnectCatalogRequest('refresh https://internal.example/catalog.json'), + ).toEqual({ + kind: 'ok', + request: { + url: 'https://internal.example/catalog.json', + preferBuiltIn: false, + allowBuiltInFallback: false, + }, + }); + expect( + resolveConnectCatalogRequest('https://internal.example/catalog.json refresh'), ).toEqual({ kind: 'ok', request: { @@ -86,38 +72,36 @@ describe('resolveConnectCatalogRequest', () => { }); }); - it('rejects --url when no value or a non-URL value is provided', () => { - const expectedMessage = - '--url requires an http(s) URL value, e.g. /connect --url=https://example.com/catalog.json'; - expect(resolveConnectCatalogRequest('--url')).toEqual({ + it('rejects unsupported flags', () => { + const flagMessage = (flag: string) => + `Unexpected flag "${flag}". Use /connect [url] [refresh] instead.`; + expect(resolveConnectCatalogRequest('--refresh')).toEqual({ kind: 'error', - message: expectedMessage, + message: flagMessage('--refresh'), }); - expect(resolveConnectCatalogRequest('--url=')).toEqual({ + expect(resolveConnectCatalogRequest('--url=https://internal.example/catalog.json')).toEqual({ kind: 'error', - message: expectedMessage, + message: flagMessage('--url=https://internal.example/catalog.json'), }); - expect(resolveConnectCatalogRequest(' --url ')).toEqual({ + expect(resolveConnectCatalogRequest('--url https://internal.example/catalog.json')).toEqual({ kind: 'error', - message: expectedMessage, + message: flagMessage('--url'), }); - expect(resolveConnectCatalogRequest('--refresh --url')).toEqual({ + }); + + it('rejects non-URL bare tokens', () => { + expect(resolveConnectCatalogRequest('ignored text')).toEqual({ kind: 'error', - message: expectedMessage, + message: 'Unknown argument "ignored". Usage: /connect [url] [refresh]', }); - // Flag-like tokens after --url must not be swallowed as the URL value. - expect(resolveConnectCatalogRequest('--url --refresh')).toEqual({ + }); + + it('rejects multiple URLs', () => { + expect( + resolveConnectCatalogRequest('https://a.com/x.json https://b.com/y.json'), + ).toEqual({ kind: 'error', - message: expectedMessage, - }); - // Plain non-URL tokens must also be rejected, not silently used. - expect(resolveConnectCatalogRequest('--url not-a-url')).toEqual({ - kind: 'error', - message: expectedMessage, - }); - expect(resolveConnectCatalogRequest('--url=ftp://example.com/x')).toEqual({ - kind: 'error', - message: expectedMessage, + message: 'Only one catalog URL can be provided. Got "https://a.com/x.json" and "https://b.com/y.json".', }); }); });