newo 1.4.0 → 1.5.0

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.
@@ -1,11 +1,46 @@
1
- import { listProjects, listAgents, listFlowSkills, updateSkill, listFlowEvents, listFlowStates, getProjectMeta } from './api.js';
2
- import { ensureState, skillPath, writeFileAtomic, readIfExists, MAP_PATH, projectDir, metadataPath } from './fsutil.js';
1
+ import {
2
+ listProjects,
3
+ listAgents,
4
+ listFlowSkills,
5
+ updateSkill,
6
+ listFlowEvents,
7
+ listFlowStates,
8
+ getProjectMeta
9
+ } from './api.js';
10
+ import {
11
+ ensureState,
12
+ skillPath,
13
+ writeFileAtomic,
14
+ readIfExists,
15
+ MAP_PATH,
16
+ projectDir,
17
+ metadataPath
18
+ } from './fsutil.js';
3
19
  import fs from 'fs-extra';
4
20
  import { sha256, loadHashes, saveHashes } from './hash.js';
5
21
  import yaml from 'js-yaml';
6
22
  import path from 'path';
23
+ import type { AxiosInstance } from 'axios';
24
+ import type {
25
+ Agent,
26
+ ProjectData,
27
+ ProjectMap,
28
+ LegacyProjectMap,
29
+ HashStore,
30
+ FlowsYamlData,
31
+ FlowsYamlAgent,
32
+ FlowsYamlFlow,
33
+ FlowsYamlSkill,
34
+ FlowsYamlEvent,
35
+ FlowsYamlState
36
+ } from './types.js';
7
37
 
8
- export async function pullSingleProject(client, projectId, projectIdn, verbose = false) {
38
+ export async function pullSingleProject(
39
+ client: AxiosInstance,
40
+ projectId: string,
41
+ projectIdn: string,
42
+ verbose: boolean = false
43
+ ): Promise<ProjectData> {
9
44
  if (verbose) console.log(`🔍 Fetching agents for project ${projectId} (${projectIdn})...`);
10
45
  const agents = await listAgents(client, projectId);
11
46
  if (verbose) console.log(`📦 Found ${agents.length} agents`);
@@ -15,28 +50,29 @@ export async function pullSingleProject(client, projectId, projectIdn, verbose =
15
50
  await writeFileAtomic(metadataPath(projectIdn), JSON.stringify(projectMeta, null, 2));
16
51
  if (verbose) console.log(`✓ Saved metadata for ${projectIdn}`);
17
52
 
18
- const projectMap = { projectId, projectIdn, agents: {} };
53
+ const projectMap: ProjectData = { projectId, projectIdn, agents: {} };
19
54
 
20
55
  for (const agent of agents) {
21
56
  const aKey = agent.idn;
22
57
  projectMap.agents[aKey] = { id: agent.id, flows: {} };
23
58
 
24
59
  for (const flow of agent.flows ?? []) {
25
- projectMap.agents[aKey].flows[flow.idn] = { id: flow.id, skills: {} };
60
+ projectMap.agents[aKey]!.flows[flow.idn] = { id: flow.id, skills: {} };
26
61
 
27
62
  const skills = await listFlowSkills(client, flow.id);
28
- for (const s of skills) {
29
- const file = skillPath(projectIdn, agent.idn, flow.idn, s.idn, s.runner_type);
30
- await writeFileAtomic(file, s.prompt_script || '');
63
+ for (const skill of skills) {
64
+ const file = skillPath(projectIdn, agent.idn, flow.idn, skill.idn, skill.runner_type);
65
+ await writeFileAtomic(file, skill.prompt_script || '');
66
+
31
67
  // Store complete skill metadata for push operations
32
- projectMap.agents[aKey].flows[flow.idn].skills[s.idn] = {
33
- id: s.id,
34
- title: s.title,
35
- idn: s.idn,
36
- runner_type: s.runner_type,
37
- model: s.model,
38
- parameters: s.parameters,
39
- path: s.path
68
+ projectMap.agents[aKey]!.flows[flow.idn]!.skills[skill.idn] = {
69
+ id: skill.id,
70
+ title: skill.title,
71
+ idn: skill.idn,
72
+ runner_type: skill.runner_type,
73
+ model: skill.model,
74
+ parameters: skill.parameters,
75
+ path: skill.path || undefined
40
76
  };
41
77
  console.log(`✓ Pulled ${file}`);
42
78
  }
@@ -50,7 +86,11 @@ export async function pullSingleProject(client, projectId, projectIdn, verbose =
50
86
  return projectMap;
51
87
  }
52
88
 
53
- export async function pullAll(client, projectId = null, verbose = false) {
89
+ export async function pullAll(
90
+ client: AxiosInstance,
91
+ projectId: string | null = null,
92
+ verbose: boolean = false
93
+ ): Promise<void> {
54
94
  await ensureState();
55
95
 
56
96
  if (projectId) {
@@ -58,11 +98,11 @@ export async function pullAll(client, projectId = null, verbose = false) {
58
98
  const projectMeta = await getProjectMeta(client, projectId);
59
99
  const projectMap = await pullSingleProject(client, projectId, projectMeta.idn, verbose);
60
100
 
61
- const idMap = { projects: { [projectMeta.idn]: projectMap } };
101
+ const idMap: ProjectMap = { projects: { [projectMeta.idn]: projectMap } };
62
102
  await fs.writeJson(MAP_PATH, idMap, { spaces: 2 });
63
103
 
64
104
  // Generate hash tracking for this project
65
- const hashes = {};
105
+ const hashes: HashStore = {};
66
106
  for (const [agentIdn, agentObj] of Object.entries(projectMap.agents)) {
67
107
  for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
68
108
  for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
@@ -77,12 +117,12 @@ export async function pullAll(client, projectId = null, verbose = false) {
77
117
  }
78
118
 
79
119
  // Multi-project mode
80
- if (verbose) console.log(`🔍 Fetching all projects...`);
120
+ if (verbose) console.log('🔍 Fetching all projects...');
81
121
  const projects = await listProjects(client);
82
122
  if (verbose) console.log(`📦 Found ${projects.length} projects`);
83
123
 
84
- const idMap = { projects: {} };
85
- const allHashes = {};
124
+ const idMap: ProjectMap = { projects: {} };
125
+ const allHashes: HashStore = {};
86
126
 
87
127
  for (const project of projects) {
88
128
  if (verbose) console.log(`\n📁 Processing project: ${project.idn} (${project.title})`);
@@ -105,24 +145,24 @@ export async function pullAll(client, projectId = null, verbose = false) {
105
145
  await saveHashes(allHashes);
106
146
  }
107
147
 
108
- export async function pushChanged(client, verbose = false) {
148
+ export async function pushChanged(client: AxiosInstance, verbose: boolean = false): Promise<void> {
109
149
  await ensureState();
110
150
  if (!(await fs.pathExists(MAP_PATH))) {
111
151
  throw new Error('Missing .newo/map.json. Run `newo pull` first.');
112
152
  }
113
153
 
114
154
  if (verbose) console.log('📋 Loading project mapping...');
115
- const idMap = await fs.readJson(MAP_PATH);
155
+ const idMap = await fs.readJson(MAP_PATH) as ProjectMap | LegacyProjectMap;
116
156
  if (verbose) console.log('🔍 Loading file hashes...');
117
157
  const oldHashes = await loadHashes();
118
- const newHashes = { ...oldHashes };
158
+ const newHashes: HashStore = { ...oldHashes };
119
159
 
120
160
  if (verbose) console.log('🔄 Scanning for changes...');
121
161
  let pushed = 0;
122
162
  let scanned = 0;
123
163
 
124
164
  // Handle both old single-project format and new multi-project format
125
- const projects = idMap.projects || { '': idMap };
165
+ const projects = 'projects' in idMap && idMap.projects ? idMap.projects : { '': idMap as ProjectData };
126
166
 
127
167
  for (const [projectIdn, projectData] of Object.entries(projects)) {
128
168
  if (verbose && projectIdn) console.log(`📁 Scanning project: ${projectIdn}`);
@@ -164,7 +204,7 @@ export async function pushChanged(client, verbose = false) {
164
204
  runner_type: skillMeta.runner_type,
165
205
  model: skillMeta.model,
166
206
  parameters: skillMeta.parameters,
167
- path: skillMeta.path
207
+ path: skillMeta.path || undefined
168
208
  };
169
209
 
170
210
  if (verbose) {
@@ -193,7 +233,7 @@ export async function pushChanged(client, verbose = false) {
193
233
  console.log(pushed ? `✅ Push complete. ${pushed} file(s) updated.` : '✅ Nothing to push.');
194
234
  }
195
235
 
196
- export async function status(verbose = false) {
236
+ export async function status(verbose: boolean = false): Promise<void> {
197
237
  await ensureState();
198
238
  if (!(await fs.pathExists(MAP_PATH))) {
199
239
  console.log('No map. Run `newo pull` first.');
@@ -201,12 +241,12 @@ export async function status(verbose = false) {
201
241
  }
202
242
 
203
243
  if (verbose) console.log('📋 Loading project mapping and hashes...');
204
- const idMap = await fs.readJson(MAP_PATH);
244
+ const idMap = await fs.readJson(MAP_PATH) as ProjectMap | LegacyProjectMap;
205
245
  const hashes = await loadHashes();
206
246
  let dirty = 0;
207
247
 
208
248
  // Handle both old single-project format and new multi-project format
209
- const projects = idMap.projects || { '': idMap };
249
+ const projects = 'projects' in idMap && idMap.projects ? idMap.projects : { '': idMap as ProjectData };
210
250
 
211
251
  for (const [projectIdn, projectData] of Object.entries(projects)) {
212
252
  if (verbose && projectIdn) console.log(`📁 Checking project: ${projectIdn}`);
@@ -248,20 +288,25 @@ export async function status(verbose = false) {
248
288
  console.log(dirty ? `${dirty} changed file(s).` : 'Clean.');
249
289
  }
250
290
 
251
- async function generateFlowsYaml(client, agents, projectIdn, verbose = false) {
252
- const flowsData = { flows: [] };
291
+ async function generateFlowsYaml(
292
+ client: AxiosInstance,
293
+ agents: Agent[],
294
+ projectIdn: string,
295
+ verbose: boolean = false
296
+ ): Promise<void> {
297
+ const flowsData: FlowsYamlData = { flows: [] };
253
298
 
254
299
  for (const agent of agents) {
255
300
  if (verbose) console.log(` 📁 Processing agent: ${agent.idn}`);
256
301
 
257
- const agentFlows = [];
302
+ const agentFlows: FlowsYamlFlow[] = [];
258
303
 
259
304
  for (const flow of agent.flows ?? []) {
260
305
  if (verbose) console.log(` 📄 Processing flow: ${flow.idn}`);
261
306
 
262
307
  // Get skills for this flow
263
308
  const skills = await listFlowSkills(client, flow.id);
264
- const skillsData = skills.map(skill => ({
309
+ const skillsData: FlowsYamlSkill[] = skills.map(skill => ({
265
310
  idn: skill.idn,
266
311
  title: skill.title || "",
267
312
  prompt_script: `flows/${flow.idn}/${skill.idn}.${skill.runner_type === 'nsl' ? 'jinja' : 'nsl'}`,
@@ -277,17 +322,17 @@ async function generateFlowsYaml(client, agents, projectIdn, verbose = false) {
277
322
  }));
278
323
 
279
324
  // Get events for this flow
280
- let eventsData = [];
325
+ let eventsData: FlowsYamlEvent[] = [];
281
326
  try {
282
327
  const events = await listFlowEvents(client, flow.id);
283
328
  eventsData = events.map(event => ({
284
329
  title: event.description,
285
330
  idn: event.idn,
286
331
  skill_selector: `!enum "SkillSelector.${event.skill_selector}"`,
287
- skill_idn: event.skill_idn,
288
- state_idn: event.state_idn,
289
- integration_idn: event.integration_idn,
290
- connector_idn: event.connector_idn,
332
+ skill_idn: event.skill_idn || undefined,
333
+ state_idn: event.state_idn || undefined,
334
+ integration_idn: event.integration_idn || undefined,
335
+ connector_idn: event.connector_idn || undefined,
291
336
  interrupt_mode: `!enum "InterruptMode.${event.interrupt_mode}"`
292
337
  }));
293
338
  if (verbose) console.log(` 📋 Found ${events.length} events`);
@@ -296,13 +341,13 @@ async function generateFlowsYaml(client, agents, projectIdn, verbose = false) {
296
341
  }
297
342
 
298
343
  // Get state fields for this flow
299
- let stateFieldsData = [];
344
+ let stateFieldsData: FlowsYamlState[] = [];
300
345
  try {
301
346
  const states = await listFlowStates(client, flow.id);
302
347
  stateFieldsData = states.map(state => ({
303
348
  title: state.title,
304
349
  idn: state.idn,
305
- default_value: state.default_value,
350
+ default_value: state.default_value || undefined,
306
351
  scope: `!enum "StateFieldScope.${state.scope}"`
307
352
  }));
308
353
  if (verbose) console.log(` 📊 Found ${states.length} state fields`);
@@ -323,11 +368,13 @@ async function generateFlowsYaml(client, agents, projectIdn, verbose = false) {
323
368
  });
324
369
  }
325
370
 
326
- flowsData.flows.push({
371
+ const agentData: FlowsYamlAgent = {
327
372
  agent_idn: agent.idn,
328
- agent_description: agent.description,
373
+ agent_description: agent.description || undefined,
329
374
  agent_flows: agentFlows
330
- });
375
+ };
376
+
377
+ flowsData.flows.push(agentData);
331
378
  }
332
379
 
333
380
  // Convert to YAML and write to file with custom enum handling
package/src/types.ts ADDED
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Comprehensive type definitions for NEWO CLI
3
+ */
4
+
5
+ export interface NewoEnvironment {
6
+ NEWO_BASE_URL?: string;
7
+ NEWO_PROJECT_ID?: string;
8
+ NEWO_API_KEY?: string;
9
+ NEWO_ACCESS_TOKEN?: string;
10
+ NEWO_REFRESH_TOKEN?: string;
11
+ NEWO_REFRESH_URL?: string;
12
+ }
13
+
14
+ // Authentication Types
15
+ export interface TokenResponse {
16
+ access_token?: string;
17
+ token?: string;
18
+ accessToken?: string;
19
+ refresh_token?: string;
20
+ refreshToken?: string;
21
+ expires_in?: number;
22
+ expiresIn?: number;
23
+ }
24
+
25
+ export interface StoredTokens {
26
+ access_token: string;
27
+ refresh_token: string;
28
+ expires_at: number;
29
+ }
30
+
31
+ // API Response Types
32
+ export interface ProjectMeta {
33
+ id: string;
34
+ idn: string;
35
+ title: string;
36
+ description?: string;
37
+ created_at?: string;
38
+ updated_at?: string;
39
+ }
40
+
41
+ export interface Agent {
42
+ id: string;
43
+ idn: string;
44
+ title?: string;
45
+ description?: string;
46
+ flows?: Flow[];
47
+ }
48
+
49
+ export interface Flow {
50
+ id: string;
51
+ idn: string;
52
+ title: string;
53
+ description?: string;
54
+ default_runner_type: RunnerType;
55
+ default_model: ModelConfig;
56
+ }
57
+
58
+ export interface ModelConfig {
59
+ model_idn: string;
60
+ provider_idn: string;
61
+ }
62
+
63
+ export interface SkillParameter {
64
+ name: string;
65
+ default_value?: string;
66
+ }
67
+
68
+ export interface Skill {
69
+ id: string;
70
+ idn: string;
71
+ title: string;
72
+ prompt_script?: string;
73
+ runner_type: RunnerType;
74
+ model: ModelConfig;
75
+ parameters: SkillParameter[];
76
+ path?: string | undefined;
77
+ }
78
+
79
+ export interface FlowEvent {
80
+ id: string;
81
+ idn: string;
82
+ description: string;
83
+ skill_selector: SkillSelector;
84
+ skill_idn?: string;
85
+ state_idn?: string;
86
+ integration_idn?: string;
87
+ connector_idn?: string;
88
+ interrupt_mode: InterruptMode;
89
+ }
90
+
91
+ export interface FlowState {
92
+ id: string;
93
+ idn: string;
94
+ title: string;
95
+ default_value?: string;
96
+ scope: StateFieldScope;
97
+ }
98
+
99
+ // Enum Types
100
+ export type RunnerType = 'guidance' | 'nsl';
101
+ export type SkillSelector = 'first' | 'last' | 'random' | 'all';
102
+ export type InterruptMode = 'allow' | 'deny' | 'queue';
103
+ export type StateFieldScope = 'flow' | 'agent' | 'project' | 'global';
104
+
105
+ // File System Types
106
+ export interface SkillMetadata {
107
+ id: string;
108
+ title: string;
109
+ idn: string;
110
+ runner_type: RunnerType;
111
+ model: ModelConfig;
112
+ parameters: SkillParameter[];
113
+ path?: string | undefined;
114
+ }
115
+
116
+ export interface FlowData {
117
+ id: string;
118
+ skills: Record<string, SkillMetadata>;
119
+ }
120
+
121
+ export interface AgentData {
122
+ id: string;
123
+ flows: Record<string, FlowData>;
124
+ }
125
+
126
+ export interface ProjectData {
127
+ projectId: string;
128
+ projectIdn: string;
129
+ agents: Record<string, AgentData>;
130
+ }
131
+
132
+ export interface ProjectMap {
133
+ projects: Record<string, ProjectData>;
134
+ }
135
+
136
+ // Legacy single-project format support
137
+ export interface LegacyProjectMap extends ProjectData {
138
+ projects?: Record<string, ProjectData>;
139
+ }
140
+
141
+ export interface HashStore {
142
+ [filePath: string]: string;
143
+ }
144
+
145
+ // AKB Types
146
+ export interface ParsedArticle {
147
+ topic_name: string;
148
+ persona_id: string | null;
149
+ topic_summary: string;
150
+ topic_facts: string[];
151
+ confidence: number;
152
+ source: string;
153
+ labels: string[];
154
+ }
155
+
156
+ export interface AkbImportArticle extends Omit<ParsedArticle, 'persona_id'> {
157
+ persona_id: string;
158
+ }
159
+
160
+ // CLI Types
161
+ export interface CliArgs {
162
+ _: string[];
163
+ verbose?: boolean;
164
+ v?: boolean;
165
+ [key: string]: unknown;
166
+ }
167
+
168
+ // flows.yaml Generation Types
169
+ export interface FlowsYamlSkill {
170
+ idn: string;
171
+ title: string;
172
+ prompt_script: string;
173
+ runner_type: string;
174
+ model: ModelConfig;
175
+ parameters: Array<{
176
+ name: string;
177
+ default_value: string;
178
+ }>;
179
+ }
180
+
181
+ export interface FlowsYamlEvent {
182
+ title: string;
183
+ idn: string;
184
+ skill_selector: string;
185
+ skill_idn?: string | undefined;
186
+ state_idn?: string | undefined;
187
+ integration_idn?: string | undefined;
188
+ connector_idn?: string | undefined;
189
+ interrupt_mode: string;
190
+ }
191
+
192
+ export interface FlowsYamlState {
193
+ title: string;
194
+ idn: string;
195
+ default_value?: string | undefined;
196
+ scope: string;
197
+ }
198
+
199
+ export interface FlowsYamlFlow {
200
+ idn: string;
201
+ title: string;
202
+ description: string | null;
203
+ default_runner_type: string;
204
+ default_provider_idn: string;
205
+ default_model_idn: string;
206
+ skills: FlowsYamlSkill[];
207
+ events: FlowsYamlEvent[];
208
+ state_fields: FlowsYamlState[];
209
+ }
210
+
211
+ export interface FlowsYamlAgent {
212
+ agent_idn: string;
213
+ agent_description?: string | undefined;
214
+ agent_flows: FlowsYamlFlow[];
215
+ }
216
+
217
+ export interface FlowsYamlData {
218
+ flows: FlowsYamlAgent[];
219
+ }
220
+
221
+ // HTTP Client Types
222
+ export interface AxiosClientConfig {
223
+ baseURL?: string;
224
+ headers?: Record<string, string>;
225
+ }
226
+
227
+ // Error Types
228
+ export interface NewoApiError extends Error {
229
+ response?: {
230
+ status: number;
231
+ data: unknown;
232
+ };
233
+ config?: {
234
+ method?: string;
235
+ url?: string;
236
+ headers?: Record<string, string>;
237
+ };
238
+ }
239
+
240
+ // Status Types
241
+ export type FileStatus = 'M' | 'D' | 'clean';
242
+
243
+ export interface StatusResult {
244
+ filePath: string;
245
+ status: FileStatus;
246
+ oldHash?: string;
247
+ newHash?: string;
248
+ }
package/src/api.js DELETED
@@ -1,103 +0,0 @@
1
- import axios from 'axios';
2
- import dotenv from 'dotenv';
3
- import { getValidAccessToken, forceReauth } from './auth.js';
4
- dotenv.config();
5
-
6
- const { NEWO_BASE_URL } = process.env;
7
-
8
- export async function makeClient(verbose = false) {
9
- let accessToken = await getValidAccessToken();
10
- if (verbose) console.log('✓ Access token obtained');
11
-
12
- const client = axios.create({
13
- baseURL: NEWO_BASE_URL,
14
- headers: { accept: 'application/json' }
15
- });
16
-
17
- client.interceptors.request.use(async (config) => {
18
- config.headers = config.headers || {};
19
- config.headers.Authorization = `Bearer ${accessToken}`;
20
- if (verbose) {
21
- console.log(`→ ${config.method?.toUpperCase()} ${config.url}`);
22
- if (config.data) console.log(' Data:', JSON.stringify(config.data, null, 2));
23
- if (config.params) console.log(' Params:', config.params);
24
- }
25
- return config;
26
- });
27
-
28
- let retried = false;
29
- client.interceptors.response.use(
30
- r => {
31
- if (verbose) {
32
- console.log(`← ${r.status} ${r.config.method?.toUpperCase()} ${r.config.url}`);
33
- if (r.data && Object.keys(r.data).length < 20) {
34
- console.log(' Response:', JSON.stringify(r.data, null, 2));
35
- } else if (r.data) {
36
- console.log(` Response: [${typeof r.data}] ${Array.isArray(r.data) ? r.data.length + ' items' : 'large object'}`);
37
- }
38
- }
39
- return r;
40
- },
41
- async (error) => {
42
- const status = error?.response?.status;
43
- if (verbose) {
44
- console.log(`← ${status} ${error.config?.method?.toUpperCase()} ${error.config?.url} - ${error.message}`);
45
- if (error.response?.data) console.log(' Error data:', error.response.data);
46
- }
47
- if (status === 401 && !retried) {
48
- retried = true;
49
- if (verbose) console.log('🔄 Retrying with fresh token...');
50
- accessToken = await forceReauth();
51
- error.config.headers.Authorization = `Bearer ${accessToken}`;
52
- return client.request(error.config);
53
- }
54
- throw error;
55
- }
56
- );
57
-
58
- return client;
59
- }
60
-
61
- export async function listProjects(client) {
62
- const r = await client.get(`/api/v1/designer/projects`);
63
- return r.data;
64
- }
65
-
66
- export async function listAgents(client, projectId) {
67
- const r = await client.get(`/api/v1/bff/agents/list`, { params: { project_id: projectId } });
68
- return r.data;
69
- }
70
-
71
- export async function getProjectMeta(client, projectId) {
72
- const r = await client.get(`/api/v1/designer/projects/by-id/${projectId}`);
73
- return r.data;
74
- }
75
-
76
- export async function listFlowSkills(client, flowId) {
77
- const r = await client.get(`/api/v1/designer/flows/${flowId}/skills`);
78
- return r.data;
79
- }
80
-
81
- export async function getSkill(client, skillId) {
82
- const r = await client.get(`/api/v1/designer/skills/${skillId}`);
83
- return r.data;
84
- }
85
-
86
- export async function updateSkill(client, skillObject) {
87
- await client.put(`/api/v1/designer/flows/skills/${skillObject.id}`, skillObject);
88
- }
89
-
90
- export async function listFlowEvents(client, flowId) {
91
- const r = await client.get(`/api/v1/designer/flows/${flowId}/events`);
92
- return r.data;
93
- }
94
-
95
- export async function listFlowStates(client, flowId) {
96
- const r = await client.get(`/api/v1/designer/flows/${flowId}/states`);
97
- return r.data;
98
- }
99
-
100
- export async function importAkbArticle(client, articleData) {
101
- const r = await client.post('/api/v1/akb/append-manual', articleData);
102
- return r.data;
103
- }