@xagent-ai/cli 1.3.0 → 1.3.1

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 (190) hide show
  1. package/.github/release.yml +76 -0
  2. package/.github/workflows/ci.yml +3 -0
  3. package/.github/workflows/release.yml +11 -17
  4. package/README.md +2 -2
  5. package/README_CN.md +2 -2
  6. package/dist/agents.d.ts.map +1 -1
  7. package/dist/agents.js +7 -3
  8. package/dist/agents.js.map +1 -1
  9. package/dist/ai-client/factory.d.ts +0 -12
  10. package/dist/ai-client/factory.d.ts.map +1 -1
  11. package/dist/ai-client/factory.js +0 -32
  12. package/dist/ai-client/factory.js.map +1 -1
  13. package/dist/ai-client/index.js +1 -1
  14. package/dist/ai-client/index.js.map +1 -1
  15. package/dist/ai-client/providers/anthropic.d.ts.map +1 -1
  16. package/dist/ai-client/providers/anthropic.js +10 -4
  17. package/dist/ai-client/providers/anthropic.js.map +1 -1
  18. package/dist/ai-client/providers/openai.d.ts.map +1 -1
  19. package/dist/ai-client/providers/openai.js +8 -4
  20. package/dist/ai-client/providers/openai.js.map +1 -1
  21. package/dist/ai-client/providers/remote.d.ts +0 -1
  22. package/dist/ai-client/providers/remote.d.ts.map +1 -1
  23. package/dist/ai-client/providers/remote.js +11 -10
  24. package/dist/ai-client/providers/remote.js.map +1 -1
  25. package/dist/ai-client/types.d.ts +14 -0
  26. package/dist/ai-client/types.d.ts.map +1 -1
  27. package/dist/ai-client/types.js +17 -0
  28. package/dist/ai-client/types.js.map +1 -1
  29. package/dist/ai-client-factory.d.ts.map +1 -1
  30. package/dist/ai-client-factory.js +4 -4
  31. package/dist/ai-client-factory.js.map +1 -1
  32. package/dist/auth.d.ts.map +1 -1
  33. package/dist/auth.js +10 -12
  34. package/dist/auth.js.map +1 -1
  35. package/dist/cancellation.d.ts.map +1 -1
  36. package/dist/cancellation.js +3 -5
  37. package/dist/cancellation.js.map +1 -1
  38. package/dist/checkpoint.d.ts +1 -0
  39. package/dist/checkpoint.d.ts.map +1 -1
  40. package/dist/checkpoint.js +37 -4
  41. package/dist/checkpoint.js.map +1 -1
  42. package/dist/cli.js +132 -32
  43. package/dist/cli.js.map +1 -1
  44. package/dist/config.js +1 -1
  45. package/dist/config.js.map +1 -1
  46. package/dist/context-compressor.d.ts +1 -2
  47. package/dist/context-compressor.d.ts.map +1 -1
  48. package/dist/context-compressor.js +23 -18
  49. package/dist/context-compressor.js.map +1 -1
  50. package/dist/conversation.d.ts +1 -1
  51. package/dist/conversation.d.ts.map +1 -1
  52. package/dist/conversation.js +8 -7
  53. package/dist/conversation.js.map +1 -1
  54. package/dist/gui-subagent/action-parser/actionParser.js +2 -2
  55. package/dist/gui-subagent/action-parser/actionParser.js.map +1 -1
  56. package/dist/gui-subagent/agent/gui-agent.d.ts +10 -0
  57. package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -1
  58. package/dist/gui-subagent/agent/gui-agent.js +105 -32
  59. package/dist/gui-subagent/agent/gui-agent.js.map +1 -1
  60. package/dist/gui-subagent/index.d.ts +7 -0
  61. package/dist/gui-subagent/index.d.ts.map +1 -1
  62. package/dist/gui-subagent/index.js +2 -0
  63. package/dist/gui-subagent/index.js.map +1 -1
  64. package/dist/gui-subagent/operator/computer-operator.d.ts.map +1 -1
  65. package/dist/gui-subagent/operator/computer-operator.js +2 -0
  66. package/dist/gui-subagent/operator/computer-operator.js.map +1 -1
  67. package/dist/input-processor.js +2 -2
  68. package/dist/input-processor.js.map +1 -1
  69. package/dist/logger.d.ts.map +1 -1
  70. package/dist/logger.js +1 -1
  71. package/dist/logger.js.map +1 -1
  72. package/dist/mcp.d.ts +2 -1
  73. package/dist/mcp.d.ts.map +1 -1
  74. package/dist/mcp.js +83 -21
  75. package/dist/mcp.js.map +1 -1
  76. package/dist/memory.d.ts.map +1 -1
  77. package/dist/memory.js +3 -3
  78. package/dist/memory.js.map +1 -1
  79. package/dist/output-util.d.ts +27 -0
  80. package/dist/output-util.d.ts.map +1 -0
  81. package/dist/output-util.js +74 -0
  82. package/dist/output-util.js.map +1 -0
  83. package/dist/retry.js +1 -1
  84. package/dist/retry.js.map +1 -1
  85. package/dist/ripgrep.d.ts.map +1 -1
  86. package/dist/ripgrep.js +5 -3
  87. package/dist/ripgrep.js.map +1 -1
  88. package/dist/sdk-output-adapter.d.ts +265 -0
  89. package/dist/sdk-output-adapter.d.ts.map +1 -0
  90. package/dist/sdk-output-adapter.js +701 -0
  91. package/dist/sdk-output-adapter.js.map +1 -0
  92. package/dist/sdk-session.d.ts +13 -0
  93. package/dist/sdk-session.d.ts.map +1 -0
  94. package/dist/sdk-session.js +50 -0
  95. package/dist/sdk-session.js.map +1 -0
  96. package/dist/session-manager.js +3 -3
  97. package/dist/session-manager.js.map +1 -1
  98. package/dist/session.d.ts +96 -2
  99. package/dist/session.d.ts.map +1 -1
  100. package/dist/session.js +849 -262
  101. package/dist/session.js.map +1 -1
  102. package/dist/shell.d.ts.map +1 -1
  103. package/dist/shell.js +5 -4
  104. package/dist/shell.js.map +1 -1
  105. package/dist/skill-installer.js +3 -3
  106. package/dist/skill-installer.js.map +1 -1
  107. package/dist/skill-invoker.d.ts +1 -1
  108. package/dist/skill-invoker.d.ts.map +1 -1
  109. package/dist/skill-invoker.js +2 -2
  110. package/dist/skill-invoker.js.map +1 -1
  111. package/dist/skill-loader.js +6 -5
  112. package/dist/skill-loader.js.map +1 -1
  113. package/dist/skill-manager.d.ts.map +1 -1
  114. package/dist/skill-manager.js +3 -2
  115. package/dist/skill-manager.js.map +1 -1
  116. package/dist/slash-commands.d.ts +1 -1
  117. package/dist/slash-commands.d.ts.map +1 -1
  118. package/dist/slash-commands.js +24 -11
  119. package/dist/slash-commands.js.map +1 -1
  120. package/dist/smart-approval.d.ts +20 -1
  121. package/dist/smart-approval.d.ts.map +1 -1
  122. package/dist/smart-approval.js +58 -1
  123. package/dist/smart-approval.js.map +1 -1
  124. package/dist/system-prompt-generator.js +3 -3
  125. package/dist/system-prompt-generator.js.map +1 -1
  126. package/dist/theme.d.ts.map +1 -1
  127. package/dist/theme.js +8 -7
  128. package/dist/theme.js.map +1 -1
  129. package/dist/tools.d.ts +15 -0
  130. package/dist/tools.d.ts.map +1 -1
  131. package/dist/tools.js +487 -215
  132. package/dist/tools.js.map +1 -1
  133. package/dist/types.d.ts +57 -0
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/types.js +49 -0
  136. package/dist/types.js.map +1 -1
  137. package/dist/update.d.ts.map +1 -1
  138. package/dist/update.js +12 -9
  139. package/dist/update.js.map +1 -1
  140. package/dist/workflow.d.ts.map +1 -1
  141. package/dist/workflow.js +1 -2
  142. package/dist/workflow.js.map +1 -1
  143. package/docs/third-party-models.md +16 -15
  144. package/package.json +3 -1
  145. package/src/agents.ts +7 -3
  146. package/src/ai-client/factory.ts +1 -36
  147. package/src/ai-client/index.ts +1 -1
  148. package/src/ai-client/providers/anthropic.ts +12 -3
  149. package/src/ai-client/providers/openai.ts +10 -4
  150. package/src/ai-client/providers/remote.ts +13 -10
  151. package/src/ai-client/types.ts +19 -0
  152. package/src/ai-client-factory.ts +5 -5
  153. package/src/auth.ts +11 -13
  154. package/src/cancellation.ts +3 -6
  155. package/src/checkpoint.ts +40 -4
  156. package/src/cli.ts +154 -37
  157. package/src/config.ts +1 -1
  158. package/src/context-compressor.ts +28 -23
  159. package/src/conversation.ts +9 -7
  160. package/src/gui-subagent/action-parser/actionParser.ts +2 -2
  161. package/src/gui-subagent/agent/gui-agent.ts +117 -34
  162. package/src/gui-subagent/index.ts +8 -0
  163. package/src/gui-subagent/operator/computer-operator.ts +2 -1
  164. package/src/input-processor.ts +2 -2
  165. package/src/logger.ts +2 -4
  166. package/src/mcp.ts +86 -23
  167. package/src/memory.ts +3 -4
  168. package/src/output-util.ts +80 -0
  169. package/src/retry.ts +1 -1
  170. package/src/ripgrep.ts +5 -3
  171. package/src/sdk-output-adapter.ts +842 -0
  172. package/src/sdk-session.ts +62 -0
  173. package/src/session-manager.ts +3 -3
  174. package/src/session.ts +942 -302
  175. package/src/shell.ts +6 -5
  176. package/src/skill-installer.ts +3 -3
  177. package/src/skill-invoker.ts +3 -4
  178. package/src/skill-loader.ts +7 -7
  179. package/src/skill-manager.ts +4 -3
  180. package/src/slash-commands.ts +24 -16
  181. package/src/smart-approval.ts +76 -1
  182. package/src/system-prompt-generator.ts +3 -3
  183. package/src/theme.ts +9 -8
  184. package/src/tools.ts +563 -267
  185. package/src/types.ts +118 -0
  186. package/src/update.ts +12 -9
  187. package/src/workflow.ts +2 -4
  188. package/test/cli-launch.test.ts +279 -0
  189. package/vitest.config.ts +2 -0
  190. /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
@@ -13,7 +13,7 @@
13
13
 
14
14
  import type { Message, CompletionOptions, CompletionResponse, AIConfig, RemoteTaskManager, Model, RemoteModelsResponse } from './ai-client/types.js';
15
15
  import { AuthConfig, AuthType } from './types.js';
16
- import { ProviderFactory, createOpenAI, createAnthropic, createRemote, type AIProvider } from './ai-client/index.js';
16
+ import { ProviderFactory, type AIProvider } from './ai-client/index.js';
17
17
  import type { RemoteAIProvider } from './ai-client/types.js';
18
18
 
19
19
  /**
@@ -62,19 +62,19 @@ class ProviderAdapter implements AIClientInterface {
62
62
  }
63
63
 
64
64
  // Optional methods - not available in local mode
65
- completeTask?(taskId: string): Promise<void> {
65
+ completeTask?(_taskId: string): Promise<void> {
66
66
  throw new Error('completeTask is only available in remote mode');
67
67
  }
68
68
 
69
- cancelTask?(taskId: string): Promise<void> {
69
+ cancelTask?(_taskId: string): Promise<void> {
70
70
  throw new Error('cancelTask is only available in remote mode');
71
71
  }
72
72
 
73
- failTask?(taskId: string, reason: 'timeout' | 'failure'): Promise<void> {
73
+ failTask?(_taskId: string, _reason: 'timeout' | 'failure'): Promise<void> {
74
74
  throw new Error('failTask is only available in remote mode');
75
75
  }
76
76
 
77
- invokeVLM?(messages: Message[], systemPrompt: string, options?: { taskId?: string; status?: 'begin' | 'continue'; signal?: AbortSignal }): Promise<string> {
77
+ invokeVLM?(_messages: Message[], _systemPrompt: string, _options?: { taskId?: string; status?: 'begin' | 'continue'; signal?: AbortSignal }): Promise<string> {
78
78
  throw new Error('invokeVLM is only available in remote mode');
79
79
  }
80
80
 
package/src/auth.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import axios from 'axios';
2
2
  import open from 'open';
3
- import { select, confirm, text, password } from '@clack/prompts';
3
+ import { select, text, password } from '@clack/prompts';
4
4
  import https from 'https';
5
5
  import { AuthConfig, AuthType } from './types.js';
6
6
  import { getLogger } from './logger.js';
@@ -59,14 +59,13 @@ export interface ThirdPartyProvider {
59
59
 
60
60
  export const THIRD_PARTY_PROVIDERS: ThirdPartyProvider[] = [
61
61
  {
62
- name: 'Zhipu AI (GLM-4)',
62
+ name: 'Zhipu AI (GLM-5)',
63
63
  baseUrl: 'https://open.bigmodel.cn/api/coding/paas/v4/',
64
- defaultModel: 'glm-4.7',
65
- description: 'Zhipu AI GLM-4 series models',
64
+ defaultModel: 'glm-5',
65
+ description: 'Zhipu AI GLM-5 series models',
66
66
  models: [
67
+ 'glm-5',
67
68
  'glm-4.7',
68
- 'glm-4',
69
- 'glm-4-plus',
70
69
  'glm-4-0520',
71
70
  'glm-4-air',
72
71
  'glm-4-airx',
@@ -105,9 +104,9 @@ export const THIRD_PARTY_PROVIDERS: ThirdPartyProvider[] = [
105
104
  {
106
105
  name: 'MiniMax',
107
106
  baseUrl: 'https://api.minimax.chat/anthropic',
108
- defaultModel: 'MiniMax-M2.1',
107
+ defaultModel: 'MiniMax-M2.5',
109
108
  description: 'MiniMax (Anthropic-compatible format)',
110
- models: ['MiniMax-M2.1', 'MiniMax-M2.1-lightning', 'MiniMax-M2', 'MiniMax-M2-Stable'],
109
+ models: ['MiniMax-M2.5', 'MiniMax-M2.1', 'MiniMax-M2.1-lightning', 'MiniMax-M2', 'MiniMax-M2-Stable'],
111
110
  },
112
111
  {
113
112
  name: '01.AI (Yi)',
@@ -182,7 +181,7 @@ export class AuthService {
182
181
  // 2. 调用后端验证用户
183
182
  const xagentApiBaseUrl = this.authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
184
183
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
185
- const response = await axios.get(`${xagentApiBaseUrl}/api/auth/me`, {
184
+ await axios.get(`${xagentApiBaseUrl}/api/auth/me`, {
186
185
  headers: { Authorization: `Bearer ${token}` },
187
186
  httpsAgent,
188
187
  });
@@ -322,7 +321,7 @@ export class AuthService {
322
321
  const response = await axios.post(
323
322
  `${this.authConfig.baseUrl}/v1/messages`,
324
323
  {
325
- model: 'MiniMax-M2',
324
+ model: 'MiniMax-M2.5',
326
325
  max_tokens: 1,
327
326
  messages: [{ role: 'user', content: 'test' }],
328
327
  },
@@ -405,14 +404,13 @@ export class AuthService {
405
404
  // Local dev: frontend runs on port 3000
406
405
  // Production: frontend is served via nginx reverse proxy at the same domain
407
406
  let loginUrl: string;
408
- let callbackUrl: string;
409
407
 
410
408
  // Always use frontend URL for login page
411
409
  const frontendUrl = isLocalDev
412
410
  ? process.env.FRONTEND_URL || 'http://localhost:3000'
413
411
  : webBaseUrl;
414
412
 
415
- callbackUrl = `${webBaseUrl}/callback`;
413
+ const callbackUrl = `${webBaseUrl}/callback`;
416
414
  loginUrl = `${frontendUrl}/login?callback=${encodeURIComponent(callbackUrl)}`;
417
415
 
418
416
  // 如果已有保存的token,通过URL参数传给Web页面
@@ -654,7 +652,7 @@ export async function selectAuthType(): Promise<AuthType> {
654
652
  { value: AuthType.OAUTH_XAGENT, label: 'Log in with xAgent – Start your free trial' },
655
653
  {
656
654
  value: AuthType.OPENAI_COMPATIBLE,
657
- label: 'Use third-party model APIs (e.g., Zhipu GLM-4.7, MiniMax)',
655
+ label: 'Use third-party model APIs (e.g., Zhipu glm-5, MiniMax)',
658
656
  },
659
657
  ],
660
658
  })) as AuthType;
@@ -29,13 +29,10 @@ export class CancellationManager extends EventEmitter {
29
29
  // Use readline's keypress handling
30
30
  readline.emitKeypressEvents(process.stdin);
31
31
 
32
- // 保存 this 引用
33
- const self = this;
34
-
35
- this.keyPressHandler = function(str: string, key: readline.Key) {
32
+ this.keyPressHandler = (str: string, key: readline.Key) => {
36
33
  // ESC 可以通过 str 为空且 name 为 'escape' 或 sequence 为 '\x1b' 来检测
37
34
  if (str === '\u001B' || key.name === 'escape' || key.sequence === '\x1b') {
38
- self.cancel();
35
+ this.cancel();
39
36
  }
40
37
  };
41
38
 
@@ -148,7 +145,7 @@ export class CancellationManager extends EventEmitter {
148
145
  });
149
146
 
150
147
  // Listen for cancellation event
151
- const onCancelled = (opId: string | null) => {
148
+ const onCancelled = (_opId: string | null) => {
152
149
  if (rejectCancellation) {
153
150
  rejectCancellation(new Error('Operation cancelled by user'));
154
151
  }
package/src/checkpoint.ts CHANGED
@@ -5,6 +5,7 @@ import { exec } from 'child_process';
5
5
  import { promisify } from 'util';
6
6
  import crypto from 'crypto';
7
7
  import { Checkpoint, ChatMessage, ToolCall } from './types.js';
8
+ import { output as logOutput } from './output-util.js';
8
9
 
9
10
  const execAsync = promisify(exec);
10
11
 
@@ -47,6 +48,7 @@ export class CheckpointManager {
47
48
  await fs.access(gitDir);
48
49
  } catch {
49
50
  console.log('Initializing shadow Git repository for checkpoints...');
51
+ await logOutput('info', 'Initializing shadow Git repository for checkpoints...');
50
52
  await execAsync('git init', { cwd: this.snapshotsDir });
51
53
  await execAsync('git config user.email "xagent@checkpoint.local"', { cwd: this.snapshotsDir });
52
54
  await execAsync('git config user.name "xAgent Checkpoint"', { cwd: this.snapshotsDir });
@@ -67,6 +69,7 @@ export class CheckpointManager {
67
69
  }
68
70
  } catch (error) {
69
71
  console.error('Failed to load existing checkpoints:', error);
72
+ await logOutput('error', 'Failed to load existing checkpoints', { error: (error as Error).message });
70
73
  }
71
74
  }
72
75
 
@@ -92,7 +95,8 @@ export class CheckpointManager {
92
95
  this.checkpoints.set(checkpointId, checkpoint);
93
96
  await this.cleanupOldCheckpoints();
94
97
 
95
- console.log(`✅ Checkpoint created: ${checkpointId}`);
98
+ console.log(`�?Checkpoint created: ${checkpointId}`);
99
+ await logOutput('success', `Checkpoint created: ${checkpointId}`);
96
100
  return checkpoint;
97
101
  }
98
102
 
@@ -103,6 +107,7 @@ export class CheckpointManager {
103
107
  await execAsync(`git tag ${checkpointId}`, { cwd: this.snapshotsDir });
104
108
  } catch (error) {
105
109
  console.error('Failed to create Git snapshot:', error);
110
+ await logOutput('error', 'Failed to create Git snapshot', { error: (error as Error).message });
106
111
  }
107
112
  }
108
113
 
@@ -121,10 +126,12 @@ export class CheckpointManager {
121
126
  console.log(`Restoring checkpoint: ${checkpointId}`);
122
127
  console.log(`Description: ${checkpoint.description}`);
123
128
  console.log(`Created: ${new Date(checkpoint.timestamp).toLocaleString()}`);
129
+ await logOutput('info', `Restoring checkpoint: ${checkpointId}`, { description: checkpoint.description, timestamp: checkpoint.timestamp });
124
130
 
125
131
  await this.restoreGitSnapshot(checkpointId);
126
132
 
127
- console.log('Checkpoint restored successfully');
133
+ console.log('�?Checkpoint restored successfully');
134
+ await logOutput('success', `Checkpoint restored successfully: ${checkpointId}`);
128
135
  }
129
136
 
130
137
  private async restoreGitSnapshot(checkpointId: string): Promise<void> {
@@ -132,6 +139,7 @@ export class CheckpointManager {
132
139
  await execAsync(`git checkout ${checkpointId}`, { cwd: this.snapshotsDir });
133
140
  } catch (error) {
134
141
  console.error('Failed to restore Git snapshot:', error);
142
+ await logOutput('error', 'Failed to restore Git snapshot', { error: (error as Error).message });
135
143
  throw new Error(`Failed to restore checkpoint: ${error}`);
136
144
  }
137
145
  }
@@ -160,12 +168,15 @@ export class CheckpointManager {
160
168
  await execAsync(`git tag -d ${checkpointId}`, { cwd: this.snapshotsDir });
161
169
  } catch (error) {
162
170
  console.warn('Failed to delete Git tag:', error);
171
+ await logOutput('warning', `Failed to delete Git tag: ${checkpointId}`);
163
172
  }
164
173
 
165
174
  this.checkpoints.delete(checkpointId);
166
- console.log(`✅ Checkpoint deleted: ${checkpointId}`);
175
+ console.log(`�?Checkpoint deleted: ${checkpointId}`);
176
+ await logOutput('success', `Checkpoint deleted: ${checkpointId}`);
167
177
  } catch (error) {
168
178
  console.error('Failed to delete checkpoint:', error);
179
+ await logOutput('error', 'Failed to delete checkpoint', { error: (error as Error).message });
169
180
  throw error;
170
181
  }
171
182
  }
@@ -184,10 +195,34 @@ export class CheckpointManager {
184
195
  await this.deleteCheckpoint(checkpoint.id);
185
196
  } catch (error) {
186
197
  console.error(`Failed to delete old checkpoint ${checkpoint.id}:`, error);
198
+ await logOutput('error', `Failed to delete old checkpoint ${checkpoint.id}`, { error: (error as Error).message });
187
199
  }
188
200
  }
189
201
  }
190
202
 
203
+ async cleanupAllCheckpoints(): Promise<void> {
204
+ try {
205
+ const checkpoints = this.listCheckpoints();
206
+
207
+ for (const checkpoint of checkpoints) {
208
+ const metadataPath = path.join(this.checkpointsDir, `${checkpoint.id}.json`);
209
+ try {
210
+ await fs.unlink(metadataPath);
211
+ } catch {
212
+ // Ignore if file doesn't exist
213
+ }
214
+
215
+ this.checkpoints.delete(checkpoint.id);
216
+ }
217
+
218
+ console.log('�?Checkpoint data cleaned up');
219
+ await logOutput('success', 'Checkpoint data cleaned up');
220
+ } catch (error) {
221
+ console.error('Failed to cleanup checkpoint data:', error);
222
+ await logOutput('error', 'Failed to cleanup checkpoint data', { error: (error as Error).message });
223
+ }
224
+ }
225
+
191
226
  isEnabled(): boolean {
192
227
  return this.enabled;
193
228
  }
@@ -201,7 +236,7 @@ export class CheckpointManager {
201
236
  await fs.rm(this.snapshotsDir, { recursive: true, force: true });
202
237
  await fs.rm(this.checkpointsDir, { recursive: true, force: true });
203
238
  this.checkpoints.clear();
204
- console.log('Checkpoint data cleaned up');
239
+ console.log('�?Checkpoint data cleaned up');
205
240
  } catch (error) {
206
241
  console.error('Failed to cleanup checkpoint data:', error);
207
242
  }
@@ -217,3 +252,4 @@ export function getCheckpointManager(projectRoot?: string, enabled?: boolean, ma
217
252
  }
218
253
  return checkpointManagerInstance!;
219
254
  }
255
+