zerg-status 0.1.0 → 1.1.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1 @@
1
+ export declare function init(name?: string): void;
@@ -0,0 +1,8 @@
1
+ import { createSession } from '../state.js';
2
+ import { formatSessionOutput } from '../output.js';
3
+ import { validateName } from '../validate.js';
4
+ export function init(name) {
5
+ const validatedName = name ? validateName(name) : undefined;
6
+ const { sessionId, name: sessionName } = createSession(validatedName);
7
+ console.log(formatSessionOutput(sessionId, sessionName));
8
+ }
@@ -0,0 +1 @@
1
+ export declare function nowCommand(sessionId: string, text: string): void;
@@ -0,0 +1,8 @@
1
+ import { setNow } from '../state.js';
2
+ import { validateNow } from '../validate.js';
3
+ import { formatStatusOutput } from '../output.js';
4
+ export function nowCommand(sessionId, text) {
5
+ const validText = validateNow(text);
6
+ const state = setNow(sessionId, validText);
7
+ console.log(formatStatusOutput(state));
8
+ }
@@ -0,0 +1 @@
1
+ export declare function stateCommand(sessionId: string, stateValue: string, question?: string): void;
@@ -0,0 +1,8 @@
1
+ import { setState } from '../state.js';
2
+ import { validateState } from '../validate.js';
3
+ import { formatStatusOutput } from '../output.js';
4
+ export function stateCommand(sessionId, stateValue, question) {
5
+ const validState = validateState(stateValue);
6
+ const state = setState(sessionId, validState, question);
7
+ console.log(formatStatusOutput(state));
8
+ }
@@ -0,0 +1 @@
1
+ export declare function summaryCommand(sessionId: string, text: string): void;
@@ -0,0 +1,8 @@
1
+ import { setSummary } from '../state.js';
2
+ import { validateSummary } from '../validate.js';
3
+ import { formatStatusOutput } from '../output.js';
4
+ export function summaryCommand(sessionId, text) {
5
+ const validText = validateSummary(text);
6
+ const state = setSummary(sessionId, validText);
7
+ console.log(formatStatusOutput(state));
8
+ }
@@ -0,0 +1 @@
1
+ export declare function taskCommand(sessionId: string, action: string, taskDescription?: string): void;
@@ -0,0 +1,25 @@
1
+ import { addTask, updateTaskStatus, clearTasks } from '../state.js';
2
+ import { validateTaskStatus } from '../validate.js';
3
+ import { formatStatusOutput } from '../output.js';
4
+ export function taskCommand(sessionId, action, taskDescription) {
5
+ if (action === 'clear') {
6
+ const state = clearTasks(sessionId);
7
+ console.log(formatStatusOutput(state));
8
+ return;
9
+ }
10
+ if (action === 'add') {
11
+ if (!taskDescription) {
12
+ throw new Error('Task description required for "add" action');
13
+ }
14
+ const state = addTask(sessionId, taskDescription, 'todo');
15
+ console.log(formatStatusOutput(state));
16
+ return;
17
+ }
18
+ // action is a task status: todo, doing, done
19
+ const status = validateTaskStatus(action);
20
+ if (!taskDescription) {
21
+ throw new Error('Task description required');
22
+ }
23
+ const state = updateTaskStatus(sessionId, taskDescription, status);
24
+ console.log(formatStatusOutput(state));
25
+ }
@@ -0,0 +1 @@
1
+ export declare function tasksCommand(sessionId: string, action: string, taskDescriptions: string[]): void;
@@ -0,0 +1,9 @@
1
+ import { setTasks } from '../state.js';
2
+ import { formatStatusOutput } from '../output.js';
3
+ export function tasksCommand(sessionId, action, taskDescriptions) {
4
+ if (action !== 'set') {
5
+ throw new Error(`Unknown tasks action: ${action}. Use "tasks set <task1> <task2> ..."`);
6
+ }
7
+ const state = setTasks(sessionId, taskDescriptions);
8
+ console.log(formatStatusOutput(state));
9
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * zerg-status - Progress reporting CLI for Zerg dashboard
4
+ *
5
+ * Usage:
6
+ * npx zerg-status init "Session Name" # Initialize session with name
7
+ * npx zerg-status init # Initialize session, outputs ZERG_SESSION:<id>
8
+ * npx zerg-status -s <id> state work # Set state to work/ask/done
9
+ * npx zerg-status -s <id> state ask "Question?" # Set state to ask with a question
10
+ * npx zerg-status -s <id> now "Headline" # Set current activity headline
11
+ * npx zerg-status -s <id> summary "Details" # Set detailed summary
12
+ * npx zerg-status -s <id> task add "Task" # Add a task
13
+ * npx zerg-status -s <id> task doing "Task" # Mark task as in progress
14
+ * npx zerg-status -s <id> task done "Task" # Mark task as complete
15
+ * npx zerg-status -s <id> task clear # Clear all tasks
16
+ * npx zerg-status -s <id> tasks set "A" "B" "C" # Replace all tasks
17
+ */
18
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * zerg-status - Progress reporting CLI for Zerg dashboard
4
+ *
5
+ * Usage:
6
+ * npx zerg-status init "Session Name" # Initialize session with name
7
+ * npx zerg-status init # Initialize session, outputs ZERG_SESSION:<id>
8
+ * npx zerg-status -s <id> state work # Set state to work/ask/done
9
+ * npx zerg-status -s <id> state ask "Question?" # Set state to ask with a question
10
+ * npx zerg-status -s <id> now "Headline" # Set current activity headline
11
+ * npx zerg-status -s <id> summary "Details" # Set detailed summary
12
+ * npx zerg-status -s <id> task add "Task" # Add a task
13
+ * npx zerg-status -s <id> task doing "Task" # Mark task as in progress
14
+ * npx zerg-status -s <id> task done "Task" # Mark task as complete
15
+ * npx zerg-status -s <id> task clear # Clear all tasks
16
+ * npx zerg-status -s <id> tasks set "A" "B" "C" # Replace all tasks
17
+ */
18
+ import { init } from './commands/init.js';
19
+ import { stateCommand } from './commands/state.js';
20
+ import { nowCommand } from './commands/now.js';
21
+ import { summaryCommand } from './commands/summary.js';
22
+ import { taskCommand } from './commands/task.js';
23
+ import { tasksCommand } from './commands/tasks.js';
24
+ import { validateSessionId, ValidationError } from './validate.js';
25
+ function parseArgs(argv) {
26
+ const args = argv.slice(2);
27
+ let sessionId;
28
+ let command = 'help';
29
+ const commandArgs = [];
30
+ let i = 0;
31
+ while (i < args.length) {
32
+ const arg = args[i];
33
+ if (arg === '-s' || arg === '--session') {
34
+ sessionId = args[i + 1];
35
+ i += 2;
36
+ continue;
37
+ }
38
+ if (!command || command === 'help') {
39
+ command = arg;
40
+ i++;
41
+ continue;
42
+ }
43
+ commandArgs.push(arg);
44
+ i++;
45
+ }
46
+ return { sessionId, command, args: commandArgs };
47
+ }
48
+ function printHelp() {
49
+ console.log(`
50
+ zerg-status - Progress reporting CLI for Zerg dashboard
51
+
52
+ COMMANDS:
53
+ init "Session Name" Initialize new session with name (outputs ZERG_SESSION:<id>:<name>)
54
+ init Initialize new session (outputs ZERG_SESSION:<id>)
55
+
56
+ -s <id> state <work|ask|done> Set session state
57
+ -s <id> state ask "Question?" Set state to ask with a question
58
+ -s <id> state done Set state to done
59
+
60
+ -s <id> now "Headline" Set current activity (max 50 chars)
61
+ -s <id> summary "Details" Set detailed summary (max 500 chars)
62
+
63
+ -s <id> task add "Task" Add a task (status: todo)
64
+ -s <id> task doing "Task" Mark task as in progress
65
+ -s <id> task done "Task" Mark task as complete
66
+ -s <id> task todo "Task" Mark task as pending
67
+ -s <id> task clear Clear all tasks
68
+
69
+ -s <id> tasks set "A" "B" "C" Replace all tasks (max 20 tasks)
70
+
71
+ EXAMPLES:
72
+ npx zerg-status init "Fix auth bug"
73
+ # ZERG_SESSION:f7a3b2c1:Fix auth bug
74
+
75
+ npx zerg-status -s f7a3b2c1 state work
76
+ npx zerg-status -s f7a3b2c1 now "Writing unit tests"
77
+ npx zerg-status -s f7a3b2c1 tasks set "Task 1" "Task 2" "Task 3"
78
+ npx zerg-status -s f7a3b2c1 task doing "Task 1"
79
+ npx zerg-status -s f7a3b2c1 task done "Task 1"
80
+ npx zerg-status -s f7a3b2c1 state done
81
+
82
+ OUTPUT:
83
+ init outputs: ZERG_SESSION:<session_id>[:<name>]
84
+ all others: ZERG_STATUS:<full_json_state>
85
+ `);
86
+ }
87
+ function main() {
88
+ try {
89
+ const { sessionId, command, args } = parseArgs(process.argv);
90
+ switch (command) {
91
+ case 'init': {
92
+ const name = args.join(' ') || undefined;
93
+ init(name);
94
+ break;
95
+ }
96
+ case 'state': {
97
+ const validSessionId = validateSessionId(sessionId);
98
+ const [stateValue, ...rest] = args;
99
+ if (!stateValue) {
100
+ throw new ValidationError('State value required. Use: work, ask, or done');
101
+ }
102
+ const question = stateValue === 'ask' ? rest.join(' ') : undefined;
103
+ stateCommand(validSessionId, stateValue, question);
104
+ break;
105
+ }
106
+ case 'now': {
107
+ const validSessionId = validateSessionId(sessionId);
108
+ const text = args.join(' ');
109
+ if (!text) {
110
+ throw new ValidationError('Now text required');
111
+ }
112
+ nowCommand(validSessionId, text);
113
+ break;
114
+ }
115
+ case 'summary': {
116
+ const validSessionId = validateSessionId(sessionId);
117
+ const text = args.join(' ');
118
+ if (!text) {
119
+ throw new ValidationError('Summary text required');
120
+ }
121
+ summaryCommand(validSessionId, text);
122
+ break;
123
+ }
124
+ case 'task': {
125
+ const validSessionId = validateSessionId(sessionId);
126
+ const [action, ...rest] = args;
127
+ if (!action) {
128
+ throw new ValidationError('Task action required. Use: add, doing, done, todo, or clear');
129
+ }
130
+ const taskDescription = rest.join(' ') || undefined;
131
+ taskCommand(validSessionId, action, taskDescription);
132
+ break;
133
+ }
134
+ case 'tasks': {
135
+ const validSessionId = validateSessionId(sessionId);
136
+ const [action, ...taskDescriptions] = args;
137
+ if (!action) {
138
+ throw new ValidationError('Tasks action required. Use: set');
139
+ }
140
+ tasksCommand(validSessionId, action, taskDescriptions);
141
+ break;
142
+ }
143
+ case 'help':
144
+ case '--help':
145
+ case '-h':
146
+ printHelp();
147
+ break;
148
+ default:
149
+ console.error(`Unknown command: ${command}`);
150
+ console.error('Run "npx zerg-status help" for usage');
151
+ process.exit(1);
152
+ }
153
+ }
154
+ catch (err) {
155
+ if (err instanceof ValidationError) {
156
+ console.error(`Error: ${err.message}`);
157
+ }
158
+ else if (err instanceof Error) {
159
+ console.error(`Error: ${err.message}`);
160
+ }
161
+ else {
162
+ console.error('Unknown error');
163
+ }
164
+ process.exit(1);
165
+ }
166
+ }
167
+ main();
@@ -0,0 +1,3 @@
1
+ import type { ZergState } from './types.js';
2
+ export declare function formatSessionOutput(sessionId: string, name?: string): string;
3
+ export declare function formatStatusOutput(state: ZergState): string;
package/dist/output.js ADDED
@@ -0,0 +1,9 @@
1
+ export function formatSessionOutput(sessionId, name) {
2
+ if (name) {
3
+ return `ZERG_SESSION:${sessionId}:${name}`;
4
+ }
5
+ return `ZERG_SESSION:${sessionId}`;
6
+ }
7
+ export function formatStatusOutput(state) {
8
+ return `ZERG_STATUS:${JSON.stringify(state)}`;
9
+ }
@@ -0,0 +1,16 @@
1
+ import type { ZergState, State, TaskStatus } from './types.js';
2
+ export declare function generateSessionId(): string;
3
+ export declare function createSession(name?: string): {
4
+ sessionId: string;
5
+ name?: string;
6
+ };
7
+ export declare function loadState(sessionId: string): ZergState;
8
+ export declare function saveState(state: ZergState): void;
9
+ export declare function updateState(sessionId: string, updates: Partial<ZergState>): ZergState;
10
+ export declare function setState(sessionId: string, newState: State, question?: string): ZergState;
11
+ export declare function setNow(sessionId: string, now: string): ZergState;
12
+ export declare function setSummary(sessionId: string, summary: string): ZergState;
13
+ export declare function addTask(sessionId: string, taskDescription: string, status?: TaskStatus): ZergState;
14
+ export declare function updateTaskStatus(sessionId: string, taskDescription: string, status: TaskStatus): ZergState;
15
+ export declare function clearTasks(sessionId: string): ZergState;
16
+ export declare function setTasks(sessionId: string, taskDescriptions: string[]): ZergState;
package/dist/state.js ADDED
@@ -0,0 +1,115 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import crypto from 'crypto';
5
+ import { DEFAULT_STATE } from './types.js';
6
+ import { validateTaskCount, validateTask } from './validate.js';
7
+ const STATE_DIR = path.join(os.homedir(), '.zerg-status');
8
+ function ensureStateDir() {
9
+ if (!fs.existsSync(STATE_DIR)) {
10
+ fs.mkdirSync(STATE_DIR, { recursive: true });
11
+ }
12
+ }
13
+ function getStatePath(sessionId) {
14
+ return path.join(STATE_DIR, `${sessionId}.json`);
15
+ }
16
+ export function generateSessionId() {
17
+ return crypto.randomBytes(4).toString('hex');
18
+ }
19
+ export function createSession(name) {
20
+ ensureStateDir();
21
+ const sessionId = generateSessionId();
22
+ const state = {
23
+ session: sessionId,
24
+ name,
25
+ ...DEFAULT_STATE,
26
+ };
27
+ fs.writeFileSync(getStatePath(sessionId), JSON.stringify(state, null, 2));
28
+ return { sessionId, name };
29
+ }
30
+ export function loadState(sessionId) {
31
+ ensureStateDir();
32
+ const statePath = getStatePath(sessionId);
33
+ if (!fs.existsSync(statePath)) {
34
+ // Create default state for this session
35
+ const state = {
36
+ session: sessionId,
37
+ ...DEFAULT_STATE,
38
+ };
39
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
40
+ return state;
41
+ }
42
+ const data = fs.readFileSync(statePath, 'utf-8');
43
+ return JSON.parse(data);
44
+ }
45
+ export function saveState(state) {
46
+ ensureStateDir();
47
+ const statePath = getStatePath(state.session);
48
+ // Atomic write: write to temp file first, then rename
49
+ const tempPath = `${statePath}.tmp`;
50
+ fs.writeFileSync(tempPath, JSON.stringify(state, null, 2));
51
+ fs.renameSync(tempPath, statePath);
52
+ }
53
+ export function updateState(sessionId, updates) {
54
+ const state = loadState(sessionId);
55
+ Object.assign(state, updates);
56
+ saveState(state);
57
+ return state;
58
+ }
59
+ export function setState(sessionId, newState, question) {
60
+ const state = loadState(sessionId);
61
+ state.state = newState;
62
+ if (newState === 'ask' && question) {
63
+ state.question = question;
64
+ }
65
+ else if (newState !== 'ask') {
66
+ delete state.question;
67
+ }
68
+ saveState(state);
69
+ return state;
70
+ }
71
+ export function setNow(sessionId, now) {
72
+ return updateState(sessionId, { now });
73
+ }
74
+ export function setSummary(sessionId, summary) {
75
+ return updateState(sessionId, { summary });
76
+ }
77
+ export function addTask(sessionId, taskDescription, status = 'todo') {
78
+ const state = loadState(sessionId);
79
+ validateTask(taskDescription);
80
+ validateTaskCount(state.tasks.length + 1);
81
+ state.tasks.push({ task: taskDescription, status });
82
+ saveState(state);
83
+ return state;
84
+ }
85
+ export function updateTaskStatus(sessionId, taskDescription, status) {
86
+ const state = loadState(sessionId);
87
+ // Find task by exact match or partial match
88
+ let task = state.tasks.find(t => t.task === taskDescription);
89
+ if (!task) {
90
+ // Try partial match
91
+ task = state.tasks.find(t => t.task.toLowerCase().includes(taskDescription.toLowerCase()));
92
+ }
93
+ if (!task) {
94
+ // Add new task with the given status
95
+ validateTask(taskDescription);
96
+ validateTaskCount(state.tasks.length + 1);
97
+ state.tasks.push({ task: taskDescription, status });
98
+ }
99
+ else {
100
+ task.status = status;
101
+ }
102
+ saveState(state);
103
+ return state;
104
+ }
105
+ export function clearTasks(sessionId) {
106
+ return updateState(sessionId, { tasks: [] });
107
+ }
108
+ export function setTasks(sessionId, taskDescriptions) {
109
+ validateTaskCount(taskDescriptions.length);
110
+ const tasks = taskDescriptions.map(task => {
111
+ validateTask(task);
112
+ return { task, status: 'todo' };
113
+ });
114
+ return updateState(sessionId, { tasks });
115
+ }
@@ -0,0 +1,16 @@
1
+ export type State = 'work' | 'ask' | 'done';
2
+ export type TaskStatus = 'todo' | 'doing' | 'done';
3
+ export interface Task {
4
+ task: string;
5
+ status: TaskStatus;
6
+ }
7
+ export interface ZergState {
8
+ session: string;
9
+ name?: string;
10
+ state: State;
11
+ now: string;
12
+ summary: string;
13
+ tasks: Task[];
14
+ question?: string;
15
+ }
16
+ export declare const DEFAULT_STATE: Omit<ZergState, 'session'>;
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ export const DEFAULT_STATE = {
2
+ state: 'work',
3
+ now: '',
4
+ summary: '',
5
+ tasks: [],
6
+ };
@@ -0,0 +1,20 @@
1
+ import type { State, TaskStatus } from './types.js';
2
+ export declare const VALID_STATES: State[];
3
+ export declare const VALID_TASK_STATUSES: TaskStatus[];
4
+ export declare const MAX_NOW_LENGTH = 50;
5
+ export declare const MAX_SUMMARY_LENGTH = 500;
6
+ export declare const MAX_TASK_LENGTH = 200;
7
+ export declare const MAX_TASKS = 20;
8
+ export declare const MAX_NAME_LENGTH = 60;
9
+ export declare const MAX_NAME_WORDS = 6;
10
+ export declare class ValidationError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ export declare function validateState(state: string): State;
14
+ export declare function validateTaskStatus(status: string): TaskStatus;
15
+ export declare function validateNow(now: string): string;
16
+ export declare function validateSummary(summary: string): string;
17
+ export declare function validateTask(task: string): string;
18
+ export declare function validateTaskCount(count: number): void;
19
+ export declare function validateSessionId(sessionId: string | undefined): string;
20
+ export declare function validateName(name: string): string;
@@ -0,0 +1,65 @@
1
+ export const VALID_STATES = ['work', 'ask', 'done'];
2
+ export const VALID_TASK_STATUSES = ['todo', 'doing', 'done'];
3
+ export const MAX_NOW_LENGTH = 50;
4
+ export const MAX_SUMMARY_LENGTH = 500;
5
+ export const MAX_TASK_LENGTH = 200;
6
+ export const MAX_TASKS = 20;
7
+ export const MAX_NAME_LENGTH = 60;
8
+ export const MAX_NAME_WORDS = 6;
9
+ export class ValidationError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'ValidationError';
13
+ }
14
+ }
15
+ export function validateState(state) {
16
+ if (!VALID_STATES.includes(state)) {
17
+ throw new ValidationError(`Invalid state: ${state}. Must be one of: ${VALID_STATES.join(', ')}`);
18
+ }
19
+ return state;
20
+ }
21
+ export function validateTaskStatus(status) {
22
+ if (!VALID_TASK_STATUSES.includes(status)) {
23
+ throw new ValidationError(`Invalid task status: ${status}. Must be one of: ${VALID_TASK_STATUSES.join(', ')}`);
24
+ }
25
+ return status;
26
+ }
27
+ export function validateNow(now) {
28
+ if (now.length > MAX_NOW_LENGTH) {
29
+ throw new ValidationError(`Now text too long (${now.length} chars). Max ${MAX_NOW_LENGTH} chars.`);
30
+ }
31
+ return now;
32
+ }
33
+ export function validateSummary(summary) {
34
+ if (summary.length > MAX_SUMMARY_LENGTH) {
35
+ throw new ValidationError(`Summary too long (${summary.length} chars). Max ${MAX_SUMMARY_LENGTH} chars.`);
36
+ }
37
+ return summary;
38
+ }
39
+ export function validateTask(task) {
40
+ if (task.length > MAX_TASK_LENGTH) {
41
+ throw new ValidationError(`Task too long (${task.length} chars). Max ${MAX_TASK_LENGTH} chars.`);
42
+ }
43
+ return task;
44
+ }
45
+ export function validateTaskCount(count) {
46
+ if (count > MAX_TASKS) {
47
+ throw new ValidationError(`Too many tasks (${count}). Max ${MAX_TASKS} tasks.`);
48
+ }
49
+ }
50
+ export function validateSessionId(sessionId) {
51
+ if (!sessionId) {
52
+ throw new ValidationError('Session ID required. Use -s <id> flag or run "npx zerg-status init" first.');
53
+ }
54
+ return sessionId;
55
+ }
56
+ export function validateName(name) {
57
+ if (name.length > MAX_NAME_LENGTH) {
58
+ throw new ValidationError(`Name too long (${name.length} chars). Max ${MAX_NAME_LENGTH} chars.`);
59
+ }
60
+ const wordCount = name.trim().split(/\s+/).length;
61
+ if (wordCount > MAX_NAME_WORDS) {
62
+ throw new ValidationError(`Name has too many words (${wordCount}). Max ${MAX_NAME_WORDS} words.`);
63
+ }
64
+ return name;
65
+ }
package/package.json CHANGED
@@ -1,38 +1,27 @@
1
1
  {
2
2
  "name": "zerg-status",
3
- "version": "0.1.0",
4
- "description": "CLI for AI coding agents to report status to a central store",
3
+ "version": "1.1.0",
4
+ "description": "Progress reporting CLI for Zerg dashboard",
5
5
  "type": "module",
6
6
  "bin": {
7
- "zerg": "./dist/cli.js"
7
+ "zerg-status": "./bin/zerg-status.js"
8
8
  },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
9
11
  "files": [
12
+ "bin",
10
13
  "dist"
11
14
  ],
12
15
  "scripts": {
13
16
  "build": "tsc",
14
- "prepublishOnly": "npm run build"
15
- },
16
- "keywords": [
17
- "ai",
18
- "agent",
19
- "status",
20
- "cli",
21
- "devin",
22
- "claude",
23
- "coding-agent"
24
- ],
25
- "author": "Exa Labs",
26
- "license": "MIT",
27
- "repository": {
28
- "type": "git",
29
- "url": "https://github.com/exa-labs/zerg"
17
+ "dev": "tsc --watch",
18
+ "test": "node --test dist/*.test.js"
30
19
  },
31
20
  "engines": {
32
21
  "node": ">=18"
33
22
  },
34
23
  "devDependencies": {
35
- "@types/node": "^20.0.0",
24
+ "@types/node": "^20.10.0",
36
25
  "typescript": "^5.3.0"
37
26
  }
38
27
  }