@oh-my-pi/pi-coding-agent 15.0.0 → 15.0.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/CHANGELOG.md +79 -0
- package/examples/extensions/plan-mode.ts +0 -1
- package/package.json +10 -10
- package/scripts/build-binary.ts +5 -0
- package/src/autoresearch/helpers.ts +17 -0
- package/src/autoresearch/tools/log-experiment.ts +9 -17
- package/src/autoresearch/tools/run-experiment.ts +2 -17
- package/src/capability/skill.ts +7 -0
- package/src/cli/list-models.ts +1 -1
- package/src/cli/shell-cli.ts +3 -13
- package/src/cli/update-cli.ts +1 -1
- package/src/cli.ts +10 -29
- package/src/commands/commit.ts +10 -0
- package/src/commit/agentic/tools/propose-changelog.ts +8 -1
- package/src/commit/analysis/conventional.ts +8 -66
- package/src/commit/map-reduce/reduce-phase.ts +6 -65
- package/src/commit/pipeline.ts +2 -2
- package/src/commit/shared-llm.ts +89 -0
- package/src/config/config-file.ts +210 -0
- package/src/config/model-equivalence.ts +8 -11
- package/src/config/model-registry.ts +44 -3
- package/src/config/model-resolver.ts +1 -4
- package/src/config/settings-schema.ts +82 -1
- package/src/config/settings.ts +1 -1
- package/src/config.ts +3 -219
- package/src/discovery/claude-plugins.ts +19 -7
- package/src/edit/renderer.ts +7 -1
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/shared/rewrite-imports.ts +2 -2
- package/src/eval/py/executor.ts +5 -0
- package/src/eval/py/runner.py +42 -11
- package/src/eval/py/runtime.ts +1 -0
- package/src/exa/factory.ts +2 -2
- package/src/exa/mcp-client.ts +74 -1
- package/src/exec/bash-executor.ts +5 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -11
- package/src/extensibility/extensions/get-commands-handler.ts +77 -0
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +89 -223
- package/src/extensibility/hooks/types.ts +89 -314
- package/src/extensibility/plugins/legacy-pi-compat.ts +48 -31
- package/src/extensibility/shared-events.ts +343 -0
- package/src/extensibility/skills.ts +9 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +500 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +237 -0
- package/src/hashline/anchors.ts +2 -2
- package/src/hashline/input.ts +2 -1
- package/src/hashline/parser.ts +27 -3
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/agent-protocol.ts +1 -20
- package/src/internal-urls/artifact-protocol.ts +1 -19
- package/src/internal-urls/docs-index.generated.ts +11 -12
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +8 -0
- package/src/internal-urls/types.ts +21 -0
- package/src/lsp/config.ts +15 -6
- package/src/lsp/defaults.json +6 -2
- package/src/main.ts +11 -2
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/modes/acp/acp-agent.ts +327 -95
- package/src/modes/components/assistant-message.ts +14 -8
- package/src/modes/components/bash-execution.ts +24 -63
- package/src/modes/components/custom-message.ts +14 -40
- package/src/modes/components/eval-execution.ts +27 -57
- package/src/modes/components/execution-shared.ts +102 -0
- package/src/modes/components/hook-message.ts +17 -49
- package/src/modes/components/mcp-add-wizard.ts +26 -5
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +6 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +93 -8
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line.ts +28 -10
- package/src/modes/components/tool-execution.ts +7 -8
- package/src/modes/controllers/command-controller-shared.ts +108 -0
- package/src/modes/controllers/command-controller.ts +13 -4
- package/src/modes/controllers/event-controller.ts +36 -7
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +13 -0
- package/src/modes/controllers/mcp-command-controller.ts +56 -61
- package/src/modes/controllers/ssh-command-controller.ts +18 -57
- package/src/modes/interactive-mode.ts +624 -52
- package/src/modes/print-mode.ts +16 -86
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-mode.ts +41 -88
- package/src/modes/rpc/rpc-types.ts +57 -0
- package/src/modes/runtime-init.ts +116 -0
- package/src/modes/theme/defaults/dark-poimandres.json +3 -0
- package/src/modes/theme/defaults/light-poimandres.json +3 -0
- package/src/modes/theme/theme.ts +24 -6
- package/src/modes/types.ts +14 -3
- package/src/modes/utils/context-usage.ts +13 -13
- package/src/modes/utils/ui-helpers.ts +10 -3
- package/src/plan-mode/approved-plan.ts +35 -1
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/system/plan-mode-active.md +5 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/tools/bash.md +6 -0
- package/src/prompts/tools/github.md +4 -4
- package/src/prompts/tools/goal.md +13 -0
- package/src/prompts/tools/hashline.md +101 -117
- package/src/prompts/tools/read.md +55 -36
- package/src/prompts/tools/resolve.md +6 -5
- package/src/sdk.ts +12 -5
- package/src/session/agent-session.ts +428 -106
- package/src/session/blob-store.ts +36 -3
- package/src/session/messages.ts +67 -2
- package/src/session/session-manager.ts +131 -12
- package/src/session/session-storage.ts +33 -15
- package/src/session/streaming-output.ts +309 -13
- package/src/slash-commands/builtin-registry.ts +18 -0
- package/src/ssh/ssh-executor.ts +5 -0
- package/src/system-prompt.ts +4 -2
- package/src/task/discovery.ts +5 -2
- package/src/task/executor.ts +19 -8
- package/src/task/index.ts +3 -0
- package/src/task/render.ts +21 -15
- package/src/task/types.ts +4 -0
- package/src/tools/ast-edit.ts +21 -120
- package/src/tools/ast-grep.ts +21 -119
- package/src/tools/bash-command-fixup.ts +47 -0
- package/src/tools/bash-interactive.ts +9 -1
- package/src/tools/bash.ts +66 -19
- package/src/tools/browser/attach.ts +3 -3
- package/src/tools/browser/launch.ts +81 -18
- package/src/tools/browser/registry.ts +1 -5
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser/tab-supervisor.ts +51 -14
- package/src/tools/conflict-detect.ts +15 -4
- package/src/tools/eval.ts +12 -2
- package/src/tools/find.ts +20 -38
- package/src/tools/gh.ts +44 -10
- package/src/tools/index.ts +22 -11
- package/src/tools/inspect-image.ts +3 -10
- package/src/tools/job.ts +16 -7
- package/src/tools/output-meta.ts +202 -37
- package/src/tools/path-utils.ts +125 -2
- package/src/tools/read.ts +548 -237
- package/src/tools/render-utils.ts +92 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +72 -44
- package/src/tools/search.ts +120 -186
- package/src/tools/ssh.ts +3 -2
- package/src/tools/write.ts +64 -9
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/image-loading.ts +7 -3
- package/src/utils/image-resize.ts +32 -43
- package/src/vim/parser.ts +0 -17
- package/src/vim/render.ts +1 -1
- package/src/vim/types.ts +1 -1
- package/src/web/search/providers/anthropic.ts +5 -0
- package/src/web/search/providers/exa.ts +3 -0
- package/src/web/search/providers/gemini.ts +40 -95
- package/src/web/search/providers/jina.ts +5 -2
- package/src/web/search/providers/zai.ts +5 -2
- package/src/prompts/tools/exit-plan-mode.md +0 -6
- package/src/tools/exit-plan-mode.ts +0 -97
- package/src/utils/fuzzy.ts +0 -108
- package/src/utils/image-convert.ts +0 -27
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
SendUserMessageHandler,
|
|
17
17
|
TerminalInputHandler,
|
|
18
18
|
} from "../../extensibility/extensions";
|
|
19
|
+
import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
|
|
19
20
|
import { HookEditorComponent } from "../../modes/components/hook-editor";
|
|
20
21
|
import { HookInputComponent } from "../../modes/components/hook-input";
|
|
21
22
|
import { HookSelectorComponent } from "../../modes/components/hook-selector";
|
|
@@ -109,7 +110,7 @@ export class ExtensionUiController {
|
|
|
109
110
|
},
|
|
110
111
|
getThinkingLevel: () => this.ctx.session.thinkingLevel,
|
|
111
112
|
setThinkingLevel: level => this.ctx.session.setThinkingLevel(level),
|
|
112
|
-
getCommands: () =>
|
|
113
|
+
getCommands: () => getSessionSlashCommands(this.ctx.session),
|
|
113
114
|
getSessionName: () => this.ctx.sessionManager.getSessionName(),
|
|
114
115
|
setSessionName: name => this.#updateSessionName(name),
|
|
115
116
|
};
|
|
@@ -349,7 +350,7 @@ export class ExtensionUiController {
|
|
|
349
350
|
},
|
|
350
351
|
getThinkingLevel: () => this.ctx.session.thinkingLevel,
|
|
351
352
|
setThinkingLevel: (level, persist) => this.ctx.session.setThinkingLevel(level, persist),
|
|
352
|
-
getCommands: () =>
|
|
353
|
+
getCommands: () => getSessionSlashCommands(this.ctx.session),
|
|
353
354
|
getSessionName: () => this.ctx.sessionManager.getSessionName(),
|
|
354
355
|
setSessionName: name => this.#updateSessionName(name),
|
|
355
356
|
};
|
|
@@ -443,6 +443,15 @@ export class InputController {
|
|
|
443
443
|
args: args || undefined,
|
|
444
444
|
lineCount: body ? body.split("\n").length : 0,
|
|
445
445
|
};
|
|
446
|
+
// When the agent is streaming, register the compact slash-form text as
|
|
447
|
+
// the pending-display twin BEFORE dispatching the CustomMessage. The
|
|
448
|
+
// returned tag is embedded in details so AgentSession.#handleAgentEvent
|
|
449
|
+
// can remove the matching display entry when the agent consumes this
|
|
450
|
+
// message (mirrors the user-message dequeue path).
|
|
451
|
+
if (this.ctx.session.isStreaming) {
|
|
452
|
+
const tag = this.ctx.session.enqueueCustomMessageDisplay(text, streamingBehavior);
|
|
453
|
+
details.__pendingDisplayTag = tag;
|
|
454
|
+
}
|
|
446
455
|
await this.ctx.session.promptCustomMessage(
|
|
447
456
|
{
|
|
448
457
|
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
@@ -453,6 +462,10 @@ export class InputController {
|
|
|
453
462
|
},
|
|
454
463
|
{ streamingBehavior },
|
|
455
464
|
);
|
|
465
|
+
if (this.ctx.session.isStreaming) {
|
|
466
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
467
|
+
this.ctx.ui.requestRender();
|
|
468
|
+
}
|
|
456
469
|
} catch (err) {
|
|
457
470
|
this.ctx.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
|
|
458
471
|
}
|
|
@@ -37,11 +37,11 @@ import type { MCPAuthConfig, MCPServerConfig, MCPServerConnection } from "../../
|
|
|
37
37
|
import type { OAuthCredential } from "../../session/auth-storage";
|
|
38
38
|
import { shortenPath } from "../../tools/render-utils";
|
|
39
39
|
import { openPath } from "../../utils/open";
|
|
40
|
-
import { DynamicBorder } from "../components/dynamic-border";
|
|
41
40
|
import { MCPAddWizard } from "../components/mcp-add-wizard";
|
|
42
41
|
import { parseCommandArgs } from "../shared";
|
|
43
42
|
import { theme } from "../theme/theme";
|
|
44
43
|
import type { InteractiveModeContext } from "../types";
|
|
44
|
+
import { groupBySource, parseRemoveArgs, readScopeFlag, showCommandMessage } from "./command-controller-shared";
|
|
45
45
|
|
|
46
46
|
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
47
47
|
const { promise: timeoutPromise, reject } = Promise.withResolvers<T>();
|
|
@@ -49,6 +49,22 @@ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string)
|
|
|
49
49
|
return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timer));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Outcome of {@link MCPCommandController}'s OAuth handler.
|
|
54
|
+
*
|
|
55
|
+
* `clientId`/`clientSecret` are populated when the OAuth provider required (or
|
|
56
|
+
* accepted) dynamic client registration; callers MUST persist them alongside
|
|
57
|
+
* `credentialId` so subsequent token refreshes and reauthorizations can reuse
|
|
58
|
+
* the same registered client. Both are also set when the caller pre-supplied a
|
|
59
|
+
* client id via the wizard or `oauth.clientId` in `mcp.json`, in which case the
|
|
60
|
+
* write-back is a no-op.
|
|
61
|
+
*/
|
|
62
|
+
interface OAuthFlowResult {
|
|
63
|
+
credentialId: string;
|
|
64
|
+
clientId?: string;
|
|
65
|
+
clientSecret?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
52
68
|
type MCPAddScope = "user" | "project";
|
|
53
69
|
type MCPAddTransport = "http" | "sse";
|
|
54
70
|
|
|
@@ -207,11 +223,11 @@ export class MCPCommandController {
|
|
|
207
223
|
break;
|
|
208
224
|
}
|
|
209
225
|
if (argToken === "--scope") {
|
|
210
|
-
const
|
|
211
|
-
if (!
|
|
212
|
-
return { scope, error:
|
|
226
|
+
const r = readScopeFlag(tokens[i + 1]);
|
|
227
|
+
if (!r.ok) {
|
|
228
|
+
return { scope, error: r.error };
|
|
213
229
|
}
|
|
214
|
-
scope =
|
|
230
|
+
scope = r.scope;
|
|
215
231
|
i += 2;
|
|
216
232
|
continue;
|
|
217
233
|
}
|
|
@@ -406,7 +422,7 @@ export class MCPCommandController {
|
|
|
406
422
|
|
|
407
423
|
try {
|
|
408
424
|
const oauthClientSecret = finalConfig.oauth?.clientSecret ?? "";
|
|
409
|
-
const
|
|
425
|
+
const oauthResult = await this.#handleOAuthFlow(
|
|
410
426
|
oauth.authorizationUrl,
|
|
411
427
|
oauth.tokenUrl,
|
|
412
428
|
oauth.clientId ?? finalConfig.oauth?.clientId ?? "",
|
|
@@ -416,14 +432,21 @@ export class MCPCommandController {
|
|
|
416
432
|
finalConfig.oauth?.callbackPath,
|
|
417
433
|
finalConfig.oauth?.redirectUri,
|
|
418
434
|
);
|
|
435
|
+
const persistedClientId = oauthResult.clientId ?? oauth.clientId ?? finalConfig.oauth?.clientId;
|
|
436
|
+
const persistedClientSecret = oauthResult.clientSecret ?? finalConfig.oauth?.clientSecret;
|
|
419
437
|
finalConfig = {
|
|
420
438
|
...finalConfig,
|
|
421
439
|
auth: {
|
|
422
440
|
type: "oauth",
|
|
423
|
-
credentialId,
|
|
441
|
+
credentialId: oauthResult.credentialId,
|
|
424
442
|
tokenUrl: oauth.tokenUrl,
|
|
425
|
-
clientId:
|
|
426
|
-
clientSecret:
|
|
443
|
+
clientId: persistedClientId,
|
|
444
|
+
clientSecret: persistedClientSecret,
|
|
445
|
+
},
|
|
446
|
+
oauth: {
|
|
447
|
+
...finalConfig.oauth,
|
|
448
|
+
clientId: persistedClientId ?? finalConfig.oauth?.clientId,
|
|
449
|
+
clientSecret: persistedClientSecret ?? finalConfig.oauth?.clientSecret,
|
|
427
450
|
},
|
|
428
451
|
};
|
|
429
452
|
} catch (oauthError) {
|
|
@@ -488,7 +511,7 @@ export class MCPCommandController {
|
|
|
488
511
|
callbackPort?: number,
|
|
489
512
|
callbackPath?: string,
|
|
490
513
|
redirectUri?: string,
|
|
491
|
-
): Promise<
|
|
514
|
+
): Promise<OAuthFlowResult> {
|
|
492
515
|
const authStorage = this.ctx.session.modelRegistry.authStorage;
|
|
493
516
|
let parsedAuthUrl: URL;
|
|
494
517
|
|
|
@@ -600,7 +623,11 @@ export class MCPCommandController {
|
|
|
600
623
|
// Store under a synthetic provider name
|
|
601
624
|
await authStorage.set(credentialId, oauthCredential);
|
|
602
625
|
|
|
603
|
-
return
|
|
626
|
+
return {
|
|
627
|
+
credentialId,
|
|
628
|
+
clientId: flow.resolvedClientId,
|
|
629
|
+
clientSecret: flow.registeredClientSecret,
|
|
630
|
+
};
|
|
604
631
|
} catch (error) {
|
|
605
632
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
606
633
|
|
|
@@ -984,23 +1011,7 @@ export class MCPCommandController {
|
|
|
984
1011
|
|
|
985
1012
|
// Show discovered servers (from .claude.json, .cursor/mcp.json, .vscode/mcp.json, etc.)
|
|
986
1013
|
if (discoveredServers.length > 0) {
|
|
987
|
-
|
|
988
|
-
const bySource = new Map<string, typeof discoveredServers>();
|
|
989
|
-
for (const entry of discoveredServers) {
|
|
990
|
-
const key = `${entry.source.providerName}|${entry.source.path}`;
|
|
991
|
-
let group = bySource.get(key);
|
|
992
|
-
if (!group) {
|
|
993
|
-
group = [];
|
|
994
|
-
bySource.set(key, group);
|
|
995
|
-
}
|
|
996
|
-
group.push(entry);
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
for (const [key, entries] of bySource) {
|
|
1000
|
-
const sepIdx = key.indexOf("|");
|
|
1001
|
-
const providerName = key.slice(0, sepIdx);
|
|
1002
|
-
const sourcePath = key.slice(sepIdx + 1);
|
|
1003
|
-
const shortPath = shortenPath(sourcePath);
|
|
1014
|
+
for (const { providerName, shortPath, items: entries } of groupBySource(discoveredServers, e => e.source)) {
|
|
1004
1015
|
lines.push(theme.fg("accent", providerName) + theme.fg("muted", ` (${shortPath}):`));
|
|
1005
1016
|
for (const { name } of entries) {
|
|
1006
1017
|
const state = this.ctx.mcpManager!.getConnectionStatus(name);
|
|
@@ -1037,32 +1048,12 @@ export class MCPCommandController {
|
|
|
1037
1048
|
async #handleRemove(text: string): Promise<void> {
|
|
1038
1049
|
const match = text.match(/^\/mcp\s+(?:remove|rm)\b\s*(.*)$/i);
|
|
1039
1050
|
const rest = match?.[1]?.trim() ?? "";
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
let scope: "project" | "user" = "project";
|
|
1044
|
-
let i = 0;
|
|
1045
|
-
|
|
1046
|
-
if (tokens.length > 0 && !tokens[0].startsWith("-")) {
|
|
1047
|
-
name = tokens[0];
|
|
1048
|
-
i = 1;
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
while (i < tokens.length) {
|
|
1052
|
-
const token = tokens[i];
|
|
1053
|
-
if (token === "--scope") {
|
|
1054
|
-
const value = tokens[i + 1];
|
|
1055
|
-
if (!value || (value !== "project" && value !== "user")) {
|
|
1056
|
-
this.ctx.showError("Invalid --scope value. Use project or user.");
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
scope = value;
|
|
1060
|
-
i += 2;
|
|
1061
|
-
continue;
|
|
1062
|
-
}
|
|
1063
|
-
this.ctx.showError(`Unknown option: ${token}`);
|
|
1051
|
+
const parsed = parseRemoveArgs(rest);
|
|
1052
|
+
if (!parsed.ok) {
|
|
1053
|
+
this.ctx.showError(parsed.error);
|
|
1064
1054
|
return;
|
|
1065
1055
|
}
|
|
1056
|
+
const { name, scope } = parsed.value;
|
|
1066
1057
|
|
|
1067
1058
|
if (!name) {
|
|
1068
1059
|
this.ctx.showError("Server name required. Usage: /mcp remove <name> [--scope project|user]");
|
|
@@ -1348,7 +1339,7 @@ export class MCPCommandController {
|
|
|
1348
1339
|
|
|
1349
1340
|
this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
|
|
1350
1341
|
|
|
1351
|
-
const
|
|
1342
|
+
const oauthResult = await this.#handleOAuthFlow(
|
|
1352
1343
|
oauth.authorizationUrl,
|
|
1353
1344
|
oauth.tokenUrl,
|
|
1354
1345
|
oauth.clientId ?? found.config.oauth?.clientId ?? "",
|
|
@@ -1359,14 +1350,22 @@ export class MCPCommandController {
|
|
|
1359
1350
|
found.config.oauth?.redirectUri,
|
|
1360
1351
|
);
|
|
1361
1352
|
|
|
1353
|
+
const persistedClientId = oauthResult.clientId ?? oauth.clientId ?? found.config.oauth?.clientId;
|
|
1354
|
+
const persistedClientSecret = oauthResult.clientSecret ?? (oauthClientSecret || undefined);
|
|
1355
|
+
|
|
1362
1356
|
const updated: MCPServerConfig = {
|
|
1363
1357
|
...baseConfig,
|
|
1364
1358
|
auth: {
|
|
1365
1359
|
type: "oauth",
|
|
1366
|
-
credentialId,
|
|
1360
|
+
credentialId: oauthResult.credentialId,
|
|
1367
1361
|
tokenUrl: oauth.tokenUrl,
|
|
1368
|
-
clientId:
|
|
1369
|
-
clientSecret:
|
|
1362
|
+
clientId: persistedClientId,
|
|
1363
|
+
clientSecret: persistedClientSecret,
|
|
1364
|
+
},
|
|
1365
|
+
oauth: {
|
|
1366
|
+
...found.config.oauth,
|
|
1367
|
+
clientId: persistedClientId ?? found.config.oauth?.clientId,
|
|
1368
|
+
clientSecret: persistedClientSecret ?? found.config.oauth?.clientSecret,
|
|
1370
1369
|
},
|
|
1371
1370
|
};
|
|
1372
1371
|
await updateMCPServer(found.filePath, name, updated);
|
|
@@ -1929,10 +1928,6 @@ export class MCPCommandController {
|
|
|
1929
1928
|
* Show a message in the chat
|
|
1930
1929
|
*/
|
|
1931
1930
|
#showMessage(text: string): void {
|
|
1932
|
-
this.ctx
|
|
1933
|
-
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
1934
|
-
this.ctx.chatContainer.addChild(new Text(text, 1, 1));
|
|
1935
|
-
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
1936
|
-
this.ctx.ui.requestRender();
|
|
1931
|
+
showCommandMessage(this.ctx, text);
|
|
1937
1932
|
}
|
|
1938
1933
|
}
|
|
@@ -3,18 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles /ssh subcommands for managing SSH host configurations.
|
|
5
5
|
*/
|
|
6
|
-
import { Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
7
6
|
import { getProjectDir, getSSHConfigPath } from "@oh-my-pi/pi-utils";
|
|
8
7
|
import { type SSHHost, sshCapability } from "../../capability/ssh";
|
|
9
8
|
import { loadCapability } from "../../discovery";
|
|
10
9
|
import { addSSHHost, readSSHConfigFile, removeSSHHost, type SSHHostConfig } from "../../ssh/config-writer";
|
|
11
|
-
import { shortenPath } from "../../tools/render-utils";
|
|
12
|
-
import { DynamicBorder } from "../components/dynamic-border";
|
|
13
10
|
import { parseCommandArgs } from "../shared";
|
|
14
11
|
import { theme } from "../theme/theme";
|
|
15
12
|
import type { InteractiveModeContext } from "../types";
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
import {
|
|
14
|
+
groupBySource,
|
|
15
|
+
parseRemoveArgs,
|
|
16
|
+
readScopeFlag,
|
|
17
|
+
type ScopeValue,
|
|
18
|
+
showCommandMessage,
|
|
19
|
+
} from "./command-controller-shared";
|
|
18
20
|
|
|
19
21
|
export class SSHCommandController {
|
|
20
22
|
constructor(private ctx: InteractiveModeContext) {}
|
|
@@ -90,7 +92,7 @@ export class SSHCommandController {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
let name: string | undefined;
|
|
93
|
-
let scope:
|
|
95
|
+
let scope: ScopeValue = "project";
|
|
94
96
|
let host: string | undefined;
|
|
95
97
|
let username: string | undefined;
|
|
96
98
|
let port: number | undefined;
|
|
@@ -167,12 +169,12 @@ export class SSHCommandController {
|
|
|
167
169
|
continue;
|
|
168
170
|
}
|
|
169
171
|
if (argToken === "--scope") {
|
|
170
|
-
const
|
|
171
|
-
if (!
|
|
172
|
-
this.ctx.showError(
|
|
172
|
+
const r = readScopeFlag(tokens[i + 1]);
|
|
173
|
+
if (!r.ok) {
|
|
174
|
+
this.ctx.showError(r.error);
|
|
173
175
|
return;
|
|
174
176
|
}
|
|
175
|
-
scope =
|
|
177
|
+
scope = r.scope;
|
|
176
178
|
i += 2;
|
|
177
179
|
continue;
|
|
178
180
|
}
|
|
@@ -300,23 +302,7 @@ export class SSHCommandController {
|
|
|
300
302
|
|
|
301
303
|
// Show discovered hosts (from ssh.json, .ssh.json in project root, etc.)
|
|
302
304
|
if (discoveredHosts.length > 0) {
|
|
303
|
-
|
|
304
|
-
const bySource = new Map<string, SSHHost[]>();
|
|
305
|
-
for (const host of discoveredHosts) {
|
|
306
|
-
const key = `${host._source.providerName}|${host._source.path}`;
|
|
307
|
-
let group = bySource.get(key);
|
|
308
|
-
if (!group) {
|
|
309
|
-
group = [];
|
|
310
|
-
bySource.set(key, group);
|
|
311
|
-
}
|
|
312
|
-
group.push(host);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
for (const [key, hosts] of bySource) {
|
|
316
|
-
const sepIdx = key.indexOf("|");
|
|
317
|
-
const providerName = key.slice(0, sepIdx);
|
|
318
|
-
const sourcePath = key.slice(sepIdx + 1);
|
|
319
|
-
const shortPath = shortenPath(sourcePath);
|
|
305
|
+
for (const { providerName, shortPath, items: hosts } of groupBySource(discoveredHosts, h => h._source)) {
|
|
320
306
|
lines.push(
|
|
321
307
|
theme.fg("accent", "Discovered") +
|
|
322
308
|
theme.fg("muted", ` (${providerName}: ${shortPath}):`) +
|
|
@@ -357,33 +343,12 @@ export class SSHCommandController {
|
|
|
357
343
|
async #handleRemove(text: string): Promise<void> {
|
|
358
344
|
const match = text.match(/^\/ssh\s+(?:remove|rm)\b\s*(.*)$/i);
|
|
359
345
|
const rest = match?.[1]?.trim() ?? "";
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
let scope: "project" | "user" = "project";
|
|
364
|
-
let i = 0;
|
|
365
|
-
|
|
366
|
-
if (tokens.length > 0 && !tokens[0].startsWith("-")) {
|
|
367
|
-
name = tokens[0];
|
|
368
|
-
i = 1;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
while (i < tokens.length) {
|
|
372
|
-
const token = tokens[i];
|
|
373
|
-
if (token === "--scope") {
|
|
374
|
-
const value = tokens[i + 1];
|
|
375
|
-
if (!value || (value !== "project" && value !== "user")) {
|
|
376
|
-
this.ctx.showError("Invalid --scope value. Use project or user.");
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
scope = value;
|
|
380
|
-
i += 2;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
this.ctx.showError(`Unknown option: ${token}`);
|
|
346
|
+
const parsed = parseRemoveArgs(rest);
|
|
347
|
+
if (!parsed.ok) {
|
|
348
|
+
this.ctx.showError(parsed.error);
|
|
384
349
|
return;
|
|
385
350
|
}
|
|
386
|
-
|
|
351
|
+
const { name, scope } = parsed.value;
|
|
387
352
|
if (!name) {
|
|
388
353
|
this.ctx.showError("Host name required. Usage: /ssh remove <name> [--scope project|user]");
|
|
389
354
|
return;
|
|
@@ -412,10 +377,6 @@ export class SSHCommandController {
|
|
|
412
377
|
* Show a message in the chat
|
|
413
378
|
*/
|
|
414
379
|
#showMessage(text: string): void {
|
|
415
|
-
this.ctx
|
|
416
|
-
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
417
|
-
this.ctx.chatContainer.addChild(new Text(text, 1, 1));
|
|
418
|
-
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
419
|
-
this.ctx.ui.requestRender();
|
|
380
|
+
showCommandMessage(this.ctx, text);
|
|
420
381
|
}
|
|
421
382
|
}
|