@robota-sdk/agent-command 3.0.0-beta.64

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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/dist/node/index.cjs +30 -0
  3. package/dist/node/index.d.ts +293 -0
  4. package/dist/node/index.d.ts.map +1 -0
  5. package/dist/node/index.js +31 -0
  6. package/dist/node/index.js.map +1 -0
  7. package/package.json +48 -0
  8. package/src/agent/__tests__/agent-command.test.ts +504 -0
  9. package/src/agent/agent-command-module.ts +82 -0
  10. package/src/agent/agent-command-parser.ts +180 -0
  11. package/src/agent/agent-command.ts +235 -0
  12. package/src/agent/index.ts +7 -0
  13. package/src/background/__tests__/background-command-module.test.ts +255 -0
  14. package/src/background/background-command-module.ts +53 -0
  15. package/src/background/background-command.ts +63 -0
  16. package/src/background/index.ts +6 -0
  17. package/src/compact/__tests__/compact-command-module.test.ts +162 -0
  18. package/src/compact/compact-command-module.ts +51 -0
  19. package/src/compact/compact-command.ts +21 -0
  20. package/src/compact/index.ts +6 -0
  21. package/src/context/__tests__/context-command-module.test.ts +294 -0
  22. package/src/context/context-command-module.ts +54 -0
  23. package/src/context/context-command.ts +298 -0
  24. package/src/context/index.ts +6 -0
  25. package/src/exit/__tests__/exit-command-module.test.ts +35 -0
  26. package/src/exit/exit-command-module.ts +48 -0
  27. package/src/exit/exit-command.ts +10 -0
  28. package/src/exit/index.ts +6 -0
  29. package/src/help/__tests__/help-command-module.test.ts +106 -0
  30. package/src/help/help-command-module.ts +48 -0
  31. package/src/help/help-command.ts +9 -0
  32. package/src/help/index.ts +6 -0
  33. package/src/index.ts +20 -0
  34. package/src/language/__tests__/language-command-module.test.ts +105 -0
  35. package/src/language/index.ts +6 -0
  36. package/src/language/language-command-module.ts +56 -0
  37. package/src/language/language-command.ts +22 -0
  38. package/src/memory/__tests__/memory-command-module.test.ts +272 -0
  39. package/src/memory/index.ts +6 -0
  40. package/src/memory/memory-command-module.ts +57 -0
  41. package/src/memory/memory-command.ts +234 -0
  42. package/src/mode/__tests__/mode-command-module.test.ts +143 -0
  43. package/src/mode/index.ts +6 -0
  44. package/src/mode/mode-command-module.ts +56 -0
  45. package/src/mode/mode-command.ts +34 -0
  46. package/src/model/__tests__/model-command-module.test.ts +273 -0
  47. package/src/model/index.ts +6 -0
  48. package/src/model/model-command-module.ts +68 -0
  49. package/src/model/model-command.ts +40 -0
  50. package/src/permissions/__tests__/permissions-command-module.test.ts +164 -0
  51. package/src/permissions/index.ts +6 -0
  52. package/src/permissions/permissions-command-module.ts +56 -0
  53. package/src/permissions/permissions-command.ts +45 -0
  54. package/src/plugin/__tests__/plugin-command-module.test.ts +214 -0
  55. package/src/plugin/index.ts +7 -0
  56. package/src/plugin/plugin-command-module.ts +81 -0
  57. package/src/plugin/plugin-command.ts +230 -0
  58. package/src/provider/__tests__/provider-command-module.test.ts +488 -0
  59. package/src/provider/__tests__/provider-setup-flow.test.ts +43 -0
  60. package/src/provider/index.ts +30 -0
  61. package/src/provider/provider-command-execution.ts +150 -0
  62. package/src/provider/provider-command-module.ts +65 -0
  63. package/src/provider/provider-command-profile-lifecycle.ts +211 -0
  64. package/src/provider/provider-command-profile-operations.ts +198 -0
  65. package/src/provider/provider-command-profile.ts +109 -0
  66. package/src/provider/provider-command-setup.ts +104 -0
  67. package/src/provider/provider-setup-flow.ts +309 -0
  68. package/src/reset/__tests__/reset-command-module.test.ts +63 -0
  69. package/src/reset/index.ts +2 -0
  70. package/src/reset/reset-command-module.ts +49 -0
  71. package/src/reset/reset-command.ts +10 -0
  72. package/src/rewind/__tests__/rewind-command-module.test.ts +215 -0
  73. package/src/rewind/index.ts +2 -0
  74. package/src/rewind/rewind-command-module.ts +57 -0
  75. package/src/rewind/rewind-command.ts +184 -0
  76. package/src/session/__tests__/session-command-module.test.ts +339 -0
  77. package/src/session/index.ts +17 -0
  78. package/src/session/session-command-module.ts +168 -0
  79. package/src/session/session-command.ts +74 -0
  80. package/src/settings/index.ts +7 -0
  81. package/src/settings/settings-command-module.ts +50 -0
  82. package/src/skills/__tests__/skills-command-module.test.ts +157 -0
  83. package/src/skills/index.ts +6 -0
  84. package/src/skills/skills-command-module.ts +62 -0
  85. package/src/skills/skills-command.ts +110 -0
  86. package/src/statusline/__tests__/statusline-command-module.test.ts +95 -0
  87. package/src/statusline/index.ts +6 -0
  88. package/src/statusline/statusline-command-module.ts +56 -0
  89. package/src/statusline/statusline-command.ts +79 -0
  90. package/src/user-local/__tests__/user-local-command.test.ts +145 -0
  91. package/src/user-local/index.ts +13 -0
  92. package/src/user-local/user-local-command-constants.ts +5 -0
  93. package/src/user-local/user-local-command-module.ts +67 -0
  94. package/src/user-local/user-local-command.ts +205 -0
  95. package/src/user-local/user-local-memory-command.ts +147 -0
@@ -0,0 +1,157 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { InteractiveSession, SystemCommandExecutor } from '@robota-sdk/agent-framework';
6
+ import type { ICommandHostContext } from '@robota-sdk/agent-framework';
7
+ import { createSkillsCommandModule } from '../skills-command-module.js';
8
+
9
+ function createTempSkill(cwd: string): void {
10
+ const skillDir = join(cwd, '.agents', 'skills', 'audit');
11
+ mkdirSync(skillDir, { recursive: true });
12
+ writeFileSync(
13
+ join(skillDir, 'SKILL.md'),
14
+ ['---', 'name: audit', 'description: Audit code', '---', 'Audit $ARGUMENTS'].join('\n'),
15
+ 'utf8',
16
+ );
17
+ }
18
+
19
+ function makeParentSession() {
20
+ return {
21
+ run: vi.fn().mockResolvedValue('parent response'),
22
+ abort: vi.fn(),
23
+ getHistory: vi.fn().mockReturnValue([]),
24
+ getContextState: vi.fn().mockReturnValue({
25
+ usedPercentage: 10,
26
+ usedTokens: 100,
27
+ maxTokens: 1000,
28
+ }),
29
+ injectMessage: vi.fn(),
30
+ getSessionId: vi.fn().mockReturnValue('parent-session-id'),
31
+ getSystemMessage: vi.fn().mockReturnValue('# system'),
32
+ getToolSchemas: vi.fn().mockReturnValue([]),
33
+ };
34
+ }
35
+
36
+ function createMockContext(overrides?: Partial<ICommandHostContext>): ICommandHostContext {
37
+ return {
38
+ getSession: vi.fn(),
39
+ getContextState: vi.fn(),
40
+ getAutoCompactThreshold: vi.fn().mockReturnValue(0.835),
41
+ compactContext: vi.fn(),
42
+ getCwd: vi.fn().mockReturnValue('/workspace'),
43
+ listEditCheckpoints: vi.fn().mockReturnValue([]),
44
+ restoreEditCheckpoint: vi.fn(),
45
+ rollbackEditCheckpoint: vi.fn(),
46
+ getUsedMemoryReferences: vi.fn().mockReturnValue([]),
47
+ recordMemoryEvent: vi.fn(),
48
+ listBackgroundTasks: vi.fn().mockReturnValue([]),
49
+ readBackgroundTaskLog: vi.fn(),
50
+ cancelBackgroundTask: vi.fn(),
51
+ closeBackgroundTask: vi.fn(),
52
+ ...overrides,
53
+ } as ICommandHostContext;
54
+ }
55
+
56
+ describe('createSkillsCommandModule', () => {
57
+ it('exposes skills as a normal model-invocable command module', () => {
58
+ const module = createSkillsCommandModule({ cwd: '/workspace' });
59
+ const command = module.systemCommands?.[0];
60
+
61
+ expect(module.name).toBe('agent-command-skills');
62
+ expect(module.commandSources?.[0]?.getCommands().map((entry) => entry.name)).toEqual([
63
+ 'skills',
64
+ ]);
65
+ expect(command).toMatchObject({
66
+ name: 'skills',
67
+ userInvocable: true,
68
+ modelInvocable: true,
69
+ lifecycle: 'inline',
70
+ argumentHint: '[list | <skill-name> [args]]',
71
+ safety: 'read-only',
72
+ });
73
+ expect(command?.description).toContain('projected skills command tool');
74
+ });
75
+
76
+ it('lists skill metadata from the SDK host context', async () => {
77
+ const executor = new SystemCommandExecutor([
78
+ ...(createSkillsCommandModule({ cwd: '/workspace' }).systemCommands ?? []),
79
+ ]);
80
+
81
+ const result = await executor.execute(
82
+ 'skills',
83
+ createMockContext({
84
+ listSkills: vi.fn().mockReturnValue([
85
+ {
86
+ name: 'repo-writing',
87
+ description: 'Repository writing rules',
88
+ source: 'skill',
89
+ modelInvocable: true,
90
+ userInvocable: true,
91
+ },
92
+ ]),
93
+ }),
94
+ '',
95
+ );
96
+
97
+ expect(result?.message).toContain('repo-writing: Repository writing rules');
98
+ expect(result?.message).toContain('Use /skills <skill-name> [args]');
99
+ expect(result?.data?.['activationContract']).toMatchObject({
100
+ activateWith: '/skills <skill-name> [args]',
101
+ activationRequiredBeforeWorkflow: true,
102
+ });
103
+ });
104
+
105
+ it('activates skills through the SDK host skill activation API', async () => {
106
+ const executeSkillCommandByName = vi.fn().mockResolvedValue({
107
+ success: true,
108
+ message: 'Skill activated: repo-writing',
109
+ data: { skill: 'repo-writing' },
110
+ });
111
+ const executor = new SystemCommandExecutor([
112
+ ...(createSkillsCommandModule({ cwd: '/workspace' }).systemCommands ?? []),
113
+ ]);
114
+
115
+ const result = await executor.execute(
116
+ 'skills',
117
+ createMockContext({
118
+ getCommandInvocationSource: vi.fn().mockReturnValue('model'),
119
+ executeSkillCommandByName,
120
+ }),
121
+ 'repo-writing update docs',
122
+ );
123
+
124
+ expect(executeSkillCommandByName).toHaveBeenCalledWith('repo-writing', 'update docs', {
125
+ invocationSource: 'model',
126
+ displayInput: '/repo-writing update docs',
127
+ rawInput: '/repo-writing update docs',
128
+ });
129
+ expect(result?.message).toBe('Skill activated: repo-writing');
130
+ });
131
+
132
+ it('lets the SDK normalize virtual /skill-name commands into the composed skills command', async () => {
133
+ const cwd = mkdtempSync(join(tmpdir(), 'robota-skills-command-module-'));
134
+ createTempSkill(cwd);
135
+ const parentSession = makeParentSession();
136
+ const session = new InteractiveSession({
137
+ session: parentSession as never,
138
+ cwd,
139
+ commandModules: [createSkillsCommandModule({ cwd })],
140
+ });
141
+
142
+ const result = await session.executeCommand('audit', 'src/index.ts');
143
+
144
+ expect(result).toMatchObject({
145
+ success: true,
146
+ data: expect.objectContaining({
147
+ skill: 'audit',
148
+ sessionExecution: true,
149
+ }),
150
+ effects: [{ type: 'session-execution-started' }],
151
+ });
152
+ expect(parentSession.run).toHaveBeenCalledWith(
153
+ expect.stringContaining('Audit src/index.ts'),
154
+ '/audit src/index.ts',
155
+ );
156
+ });
157
+ });
@@ -0,0 +1,6 @@
1
+ export {
2
+ createSkillsCommandEntry,
3
+ createSkillsCommandModule,
4
+ SkillsCommandSource,
5
+ } from './skills-command-module.js';
6
+ export { executeSkillsCommand, SKILLS_COMMAND_DESCRIPTION } from './skills-command.js';
@@ -0,0 +1,62 @@
1
+ import type {
2
+ ICommand,
3
+ ICommandModule,
4
+ ICommandSource,
5
+ ISystemCommand,
6
+ } from '@robota-sdk/agent-framework';
7
+ import { SkillCommandSource } from '@robota-sdk/agent-framework';
8
+ import { executeSkillsCommand, SKILLS_COMMAND_DESCRIPTION } from './skills-command.js';
9
+
10
+ export interface ISkillsCommandModuleOptions {
11
+ readonly cwd?: string;
12
+ }
13
+
14
+ export function createSkillsCommandEntry(): ICommand {
15
+ return {
16
+ name: 'skills',
17
+ displayName: 'Skills',
18
+ description: SKILLS_COMMAND_DESCRIPTION,
19
+ source: 'skills',
20
+ modelInvocable: true,
21
+ userInvocable: true,
22
+ argumentHint: '[list | <skill-name> [args]]',
23
+ safety: 'read-only',
24
+ };
25
+ }
26
+
27
+ function createSkillsSystemCommand(): ISystemCommand {
28
+ const entry = createSkillsCommandEntry();
29
+ return {
30
+ name: entry.name,
31
+ displayName: entry.displayName,
32
+ description: entry.description,
33
+ requiresPermission: false,
34
+ userInvocable: true,
35
+ modelInvocable: true,
36
+ argumentHint: entry.argumentHint,
37
+ safety: entry.safety,
38
+ lifecycle: 'inline',
39
+ execute: executeSkillsCommand,
40
+ };
41
+ }
42
+
43
+ export class SkillsCommandSource implements ICommandSource {
44
+ readonly name = 'skills';
45
+
46
+ getCommands(): ICommand[] {
47
+ return [createSkillsCommandEntry()];
48
+ }
49
+ }
50
+
51
+ export function createSkillsCommandModule(
52
+ options: ISkillsCommandModuleOptions = {},
53
+ ): ICommandModule {
54
+ const commandSources: ICommandSource[] = [new SkillsCommandSource()];
55
+ commandSources.push(new SkillCommandSource(options.cwd ?? process.cwd()));
56
+
57
+ return {
58
+ name: 'agent-command-skills',
59
+ commandSources,
60
+ systemCommands: [createSkillsSystemCommand()],
61
+ };
62
+ }
@@ -0,0 +1,110 @@
1
+ import type {
2
+ ICommandHostContext,
3
+ ICommandResult,
4
+ ICommandSkillListEntry,
5
+ } from '@robota-sdk/agent-framework';
6
+
7
+ export const SKILLS_COMMAND_DESCRIPTION =
8
+ 'Skill command. Before following a matching registered skill from the system prompt Skills section, invoke the projected skills command tool with args "<skill-name> [args]". Without arguments, list registered skills. With a skill name, activate that skill. Slash syntax is a UI input/display concern; the SDK command identity is "skills".';
9
+
10
+ interface IParsedSkillsArgs {
11
+ readonly action: 'list' | 'activate';
12
+ readonly skillName?: string;
13
+ readonly skillArgs: string;
14
+ }
15
+
16
+ function formatSkillFlags(skill: ICommandSkillListEntry): string {
17
+ const flags: string[] = [];
18
+ if (!skill.modelInvocable) flags.push('model-disabled');
19
+ if (!skill.userInvocable) flags.push('model-only');
20
+ if (skill.context) flags.push(`context:${skill.context}`);
21
+ if (skill.agent) flags.push(`agent:${skill.agent}`);
22
+ return flags.length > 0 ? ` [${flags.join(', ')}]` : '';
23
+ }
24
+
25
+ function formatSkillLine(skill: ICommandSkillListEntry): string {
26
+ const hint = skill.argumentHint ? ` ${skill.argumentHint}` : '';
27
+ return `- ${skill.name}${hint}: ${skill.description}${formatSkillFlags(skill)}`;
28
+ }
29
+
30
+ function formatSkillsMessage(skills: readonly ICommandSkillListEntry[]): string {
31
+ if (skills.length === 0) {
32
+ return [
33
+ 'No skills are registered for this session.',
34
+ '',
35
+ 'Skills are metadata until activated. Do not invent or imitate a skill workflow when no matching registered skill exists.',
36
+ ].join('\n');
37
+ }
38
+
39
+ return [
40
+ 'Registered skills:',
41
+ ...skills.map(formatSkillLine),
42
+ '',
43
+ 'Activation contract:',
44
+ '- Use /skills <skill-name> [args] to activate a matching skill.',
45
+ '- Treat /<skill-name> as a virtual alias for /skills <skill-name>.',
46
+ '- The system prompt Skills section is skill selection metadata.',
47
+ '- Treat descriptions as selection metadata only, not as loaded SKILL.md content.',
48
+ '- Do not answer by merely naming, recommending, or imitating a matching skill.',
49
+ '- If no listed skill matches the task, continue without claiming a skill was activated.',
50
+ ].join('\n');
51
+ }
52
+
53
+ function parseSkillsArgs(args: string): IParsedSkillsArgs {
54
+ const trimmed = args.trim();
55
+ if (trimmed.length === 0 || trimmed === 'list') {
56
+ return { action: 'list', skillArgs: '' };
57
+ }
58
+
59
+ const [skillName = '', ...rest] = trimmed.split(/\s+/);
60
+ if (skillName.length === 0) {
61
+ return { action: 'list', skillArgs: '' };
62
+ }
63
+
64
+ return {
65
+ action: 'activate',
66
+ skillName,
67
+ skillArgs: rest.join(' '),
68
+ };
69
+ }
70
+
71
+ export async function executeSkillsCommand(
72
+ context: ICommandHostContext,
73
+ args = '',
74
+ ): Promise<ICommandResult> {
75
+ const parsed = parseSkillsArgs(args);
76
+ if (parsed.action === 'activate' && parsed.skillName !== undefined) {
77
+ if (!context.executeSkillCommandByName) {
78
+ return {
79
+ success: false,
80
+ message: 'Skill activation is not available in this session.',
81
+ };
82
+ }
83
+ const displayInput = `/${parsed.skillName}${parsed.skillArgs ? ` ${parsed.skillArgs}` : ''}`;
84
+ const result = await context.executeSkillCommandByName(parsed.skillName, parsed.skillArgs, {
85
+ invocationSource: context.getCommandInvocationSource?.() ?? 'user',
86
+ displayInput,
87
+ rawInput: displayInput,
88
+ });
89
+ return (
90
+ result ?? {
91
+ success: false,
92
+ message: `Unknown skill: ${parsed.skillName}`,
93
+ }
94
+ );
95
+ }
96
+
97
+ const skills = context.listSkills?.() ?? [];
98
+ return {
99
+ success: true,
100
+ message: formatSkillsMessage(skills),
101
+ data: {
102
+ skills,
103
+ activationContract: {
104
+ activateWith: '/skills <skill-name> [args]',
105
+ activationRequiredBeforeWorkflow: true,
106
+ metadataIsNotSkillContent: true,
107
+ },
108
+ },
109
+ };
110
+ }
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { ICommandHostContext } from '@robota-sdk/agent-framework';
3
+ import { SystemCommandExecutor } from '@robota-sdk/agent-framework';
4
+ import { createStatusLineCommandModule } from '../statusline-command-module.js';
5
+
6
+ const COMMAND_CONTEXT = {} as ICommandHostContext;
7
+
8
+ describe('createStatusLineCommandModule', () => {
9
+ it('provides statusline metadata and user-only executable command from one module owner', () => {
10
+ const module = createStatusLineCommandModule();
11
+ const command = module.systemCommands?.[0];
12
+ const entry = module.commandSources?.[0]?.getCommands()[0];
13
+
14
+ expect(module.name).toBe('agent-command-statusline');
15
+ expect(entry).toEqual(
16
+ expect.objectContaining({
17
+ name: 'statusline',
18
+ description:
19
+ 'Configure TUI status-line visibility and fields such as model, context, tokens, session, and git branch.',
20
+ source: 'statusline',
21
+ argumentHint: 'on | off | reset | git on | git off',
22
+ modelInvocable: false,
23
+ }),
24
+ );
25
+ expect(entry?.subcommands?.map((subcommand) => subcommand.name)).toEqual([
26
+ 'on',
27
+ 'off',
28
+ 'reset',
29
+ 'git',
30
+ ]);
31
+ expect(command).toEqual(
32
+ expect.objectContaining({
33
+ name: 'statusline',
34
+ lifecycle: 'inline',
35
+ userInvocable: true,
36
+ modelInvocable: false,
37
+ }),
38
+ );
39
+ });
40
+
41
+ it.each([
42
+ {
43
+ args: 'on',
44
+ message: 'Status line enabled.',
45
+ patch: { enabled: true },
46
+ },
47
+ {
48
+ args: 'off',
49
+ message: 'Status line disabled.',
50
+ patch: { enabled: false },
51
+ },
52
+ {
53
+ args: 'reset',
54
+ message: 'Status line settings reset.',
55
+ patch: { enabled: true, gitBranch: true },
56
+ },
57
+ {
58
+ args: 'git on',
59
+ message: 'Status line git branch shown.',
60
+ patch: { gitBranch: true },
61
+ },
62
+ {
63
+ args: 'git off',
64
+ message: 'Status line git branch hidden.',
65
+ patch: { gitBranch: false },
66
+ },
67
+ ])(
68
+ 'emits a statusline settings patch for /statusline $args',
69
+ async ({ args, message, patch }) => {
70
+ const executor = new SystemCommandExecutor([
71
+ ...(createStatusLineCommandModule().systemCommands ?? []),
72
+ ]);
73
+
74
+ const result = await executor.execute('statusline', COMMAND_CONTEXT, args);
75
+
76
+ expect(result).toEqual({
77
+ success: true,
78
+ message,
79
+ effects: [{ type: 'statusline-settings-patch', patch }],
80
+ });
81
+ },
82
+ );
83
+
84
+ it('reports usage for unsupported arguments without effects', async () => {
85
+ const executor = new SystemCommandExecutor([
86
+ ...(createStatusLineCommandModule().systemCommands ?? []),
87
+ ]);
88
+
89
+ const result = await executor.execute('statusline', COMMAND_CONTEXT, 'wat');
90
+
91
+ expect(result?.success).toBe(false);
92
+ expect(result?.message).toContain('Usage: /statusline');
93
+ expect(result?.effects).toBeUndefined();
94
+ });
95
+ });
@@ -0,0 +1,6 @@
1
+ export {
2
+ createStatusLineCommandEntry,
3
+ createStatusLineCommandModule,
4
+ StatusLineCommandSource,
5
+ } from './statusline-command-module.js';
6
+ export { executeStatusLineCommand, STATUSLINE_USAGE } from './statusline-command.js';
@@ -0,0 +1,56 @@
1
+ import type {
2
+ ICommand,
3
+ ICommandModule,
4
+ ICommandSource,
5
+ ISystemCommand,
6
+ } from '@robota-sdk/agent-framework';
7
+ import {
8
+ buildStatusLineCommandSubcommands,
9
+ STATUSLINE_COMMAND_ARGUMENT_HINT,
10
+ STATUSLINE_COMMAND_DESCRIPTION,
11
+ } from '@robota-sdk/agent-framework';
12
+ import { executeStatusLineCommand } from './statusline-command.js';
13
+
14
+ export function createStatusLineCommandEntry(): ICommand {
15
+ return {
16
+ name: 'statusline',
17
+ displayName: 'Status Line',
18
+ description: STATUSLINE_COMMAND_DESCRIPTION,
19
+ source: 'statusline',
20
+ argumentHint: STATUSLINE_COMMAND_ARGUMENT_HINT,
21
+ subcommands: buildStatusLineCommandSubcommands('statusline'),
22
+ modelInvocable: false,
23
+ };
24
+ }
25
+
26
+ function createStatusLineSystemCommand(): ISystemCommand {
27
+ const entry = createStatusLineCommandEntry();
28
+ return {
29
+ name: entry.name,
30
+ displayName: entry.displayName,
31
+ description: entry.description,
32
+ requiresPermission: false,
33
+ userInvocable: true,
34
+ modelInvocable: false,
35
+ argumentHint: entry.argumentHint,
36
+ subcommands: entry.subcommands,
37
+ lifecycle: 'inline',
38
+ execute: executeStatusLineCommand,
39
+ };
40
+ }
41
+
42
+ export class StatusLineCommandSource implements ICommandSource {
43
+ readonly name = 'statusline';
44
+
45
+ getCommands(): ICommand[] {
46
+ return [createStatusLineCommandEntry()];
47
+ }
48
+ }
49
+
50
+ export function createStatusLineCommandModule(): ICommandModule {
51
+ return {
52
+ name: 'agent-command-statusline',
53
+ commandSources: [new StatusLineCommandSource()],
54
+ systemCommands: [createStatusLineSystemCommand()],
55
+ };
56
+ }
@@ -0,0 +1,79 @@
1
+ import type {
2
+ ICommandHostContext,
3
+ ICommandResult,
4
+ TStatusLineCommandSettingsPatch,
5
+ } from '@robota-sdk/agent-framework';
6
+ import { DEFAULT_STATUS_LINE_COMMAND_SETTINGS } from '@robota-sdk/agent-framework';
7
+
8
+ interface IStatusLineCommandSuccessAction {
9
+ success: true;
10
+ message: string;
11
+ patch: TStatusLineCommandSettingsPatch;
12
+ }
13
+
14
+ interface IStatusLineCommandFailureAction {
15
+ success: false;
16
+ message: string;
17
+ }
18
+
19
+ type TStatusLineCommandAction = IStatusLineCommandSuccessAction | IStatusLineCommandFailureAction;
20
+
21
+ export const STATUSLINE_USAGE = [
22
+ 'Usage: /statusline on | off | reset | git on | git off',
23
+ 'Fields: model, context, permission mode, message count, session name, thinking state, git branch.',
24
+ ].join('\n');
25
+
26
+ function parseStatusLineArgs(args: string): TStatusLineCommandAction {
27
+ const parts = args
28
+ .trim()
29
+ .toLowerCase()
30
+ .split(/\s+/)
31
+ .filter((part) => part.length > 0);
32
+ const [first, second] = parts;
33
+
34
+ if (first === 'on' && second === undefined) {
35
+ return { success: true, message: 'Status line enabled.', patch: { enabled: true } };
36
+ }
37
+ if (first === 'off' && second === undefined) {
38
+ return { success: true, message: 'Status line disabled.', patch: { enabled: false } };
39
+ }
40
+ if (first === 'reset' && second === undefined) {
41
+ return {
42
+ success: true,
43
+ message: 'Status line settings reset.',
44
+ patch: { ...DEFAULT_STATUS_LINE_COMMAND_SETTINGS },
45
+ };
46
+ }
47
+ if (first === 'git' && second === 'on' && parts.length === 2) {
48
+ return {
49
+ success: true,
50
+ message: 'Status line git branch shown.',
51
+ patch: { gitBranch: true },
52
+ };
53
+ }
54
+ if (first === 'git' && second === 'off' && parts.length === 2) {
55
+ return {
56
+ success: true,
57
+ message: 'Status line git branch hidden.',
58
+ patch: { gitBranch: false },
59
+ };
60
+ }
61
+
62
+ return { success: false, message: STATUSLINE_USAGE };
63
+ }
64
+
65
+ export function executeStatusLineCommand(
66
+ _context: ICommandHostContext,
67
+ args: string,
68
+ ): ICommandResult {
69
+ const action = parseStatusLineArgs(args);
70
+ if (!action.success) {
71
+ return { success: false, message: action.message };
72
+ }
73
+
74
+ return {
75
+ success: true,
76
+ message: action.message,
77
+ effects: [{ type: 'statusline-settings-patch', patch: action.patch }],
78
+ };
79
+ }