gibi-bot 1.0.0 → 1.1.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.
- package/.context.json +47 -3
- package/.github/workflows/npm-publish.yml +33 -0
- package/.github/workflows/release.yml +45 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +2 -0
- package/.prettierignore +3 -0
- package/.prettierrc +7 -0
- package/CHANGELOG.md +45 -0
- package/DISTRIBUTION.md +9 -1
- package/GEMINI.md +28 -9
- package/README.md +55 -28
- package/commitlint.config.js +3 -0
- package/conductor/code_styleguides/general.md +6 -1
- package/conductor/code_styleguides/ts.md +42 -35
- package/conductor/product-guidelines.md +16 -12
- package/conductor/product.md +12 -7
- package/conductor/setup_state.json +1 -1
- package/conductor/tech-stack.md +4 -1
- package/conductor/tracks/slack_bot_20260107/metadata.json +1 -1
- package/conductor/tracks/slack_bot_20260107/plan.md +6 -1
- package/conductor/tracks/slack_bot_20260107/spec.md +9 -6
- package/conductor/tracks.md +2 -1
- package/conductor/workflow.md +74 -53
- package/dist/agents.js +7 -10
- package/dist/agents.test.js +17 -12
- package/dist/app.js +212 -135
- package/dist/config.js +5 -5
- package/dist/context.js +4 -3
- package/dist/context.test.js +2 -4
- package/eslint.config.mjs +17 -0
- package/jest.config.js +4 -3
- package/nodemon.json +5 -9
- package/package.json +34 -4
- package/release.config.js +22 -0
- package/src/agents.test.ts +62 -57
- package/src/agents.ts +94 -82
- package/src/app.d.ts +1 -1
- package/src/app.ts +298 -178
- package/src/config.ts +48 -48
- package/src/context.test.ts +54 -56
- package/src/context.ts +123 -114
- package/test_gemini.js +13 -9
- package/test_gemini_approval.js +13 -9
- package/test_gemini_write.js +19 -9
- package/tests/context.test.ts +145 -0
- package/tsconfig.json +1 -1
- package/src/app.js +0 -55
- package/src/app.js.map +0 -1
package/src/config.ts
CHANGED
|
@@ -7,66 +7,66 @@ dotenv.config();
|
|
|
7
7
|
const SERVICE_NAME = 'gibi-slack-bot';
|
|
8
8
|
|
|
9
9
|
const REQUIRED_VARS = [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
{ name: 'SLACK_BOT_TOKEN', message: 'Enter your Slack Bot Token (xoxb-...):' },
|
|
11
|
+
{ name: 'SLACK_SIGNING_SECRET', message: 'Enter your Slack Signing Secret:' },
|
|
12
|
+
{ name: 'SLACK_APP_TOKEN', message: 'Enter your Slack App Token (xapp-...):' },
|
|
13
13
|
];
|
|
14
14
|
|
|
15
15
|
export async function loadConfig(): Promise<void> {
|
|
16
|
-
|
|
16
|
+
let updated = false;
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
for (const { name, message } of REQUIRED_VARS) {
|
|
19
|
+
if (process.env[name]) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
// Try fetching from keychain
|
|
24
|
+
let storedValue: string | null = null;
|
|
25
|
+
try {
|
|
26
|
+
storedValue = await keytar.getPassword(SERVICE_NAME, name);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.warn(`[WARN] Failed to access keychain for ${name}:`, err);
|
|
29
|
+
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
if (storedValue) {
|
|
32
|
+
process.env[name] = storedValue;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
// Prompt user
|
|
37
|
+
const response = await inquirer.prompt([
|
|
38
|
+
{
|
|
39
|
+
type: 'password',
|
|
40
|
+
name: 'value',
|
|
41
|
+
message: message,
|
|
42
|
+
mask: '*',
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
46
|
+
const value = response.value as string;
|
|
47
|
+
if (value) {
|
|
48
|
+
process.env[name] = value;
|
|
49
|
+
await keytar.setPassword(SERVICE_NAME, name, value);
|
|
50
|
+
updated = true;
|
|
51
|
+
} else {
|
|
52
|
+
console.error(`Error: ${name} is required.`);
|
|
53
|
+
process.exit(1);
|
|
55
54
|
}
|
|
55
|
+
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
if (updated) {
|
|
58
|
+
console.log('Configuration saved to keychain for future runs.');
|
|
59
|
+
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export async function clearConfig(keysToClear?: string[]): Promise<void> {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
const varsToProcess = keysToClear
|
|
64
|
+
? REQUIRED_VARS.filter((v) => keysToClear.includes(v.name))
|
|
65
|
+
: REQUIRED_VARS;
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
for (const { name } of varsToProcess) {
|
|
68
|
+
await keytar.deletePassword(SERVICE_NAME, name);
|
|
69
|
+
delete process.env[name];
|
|
70
|
+
}
|
|
71
|
+
console.log(`Cleared stored configuration for: ${varsToProcess.map((v) => v.name).join(', ')}`);
|
|
72
72
|
}
|
package/src/context.test.ts
CHANGED
|
@@ -1,75 +1,73 @@
|
|
|
1
1
|
import { ContextManager } from './context';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
3
|
|
|
5
4
|
jest.mock('fs');
|
|
6
5
|
|
|
7
6
|
describe('ContextManager', () => {
|
|
8
|
-
|
|
9
|
-
const mockStoragePath = path.join(process.cwd(), '.context.json');
|
|
7
|
+
let contextManager: ContextManager;
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
(fs.existsSync as jest.Mock).mockReturnValue(false);
|
|
12
|
+
contextManager = new ContextManager();
|
|
13
|
+
});
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
it('should create a new context if it does not exist', () => {
|
|
16
|
+
const contextId = 'test-id';
|
|
17
|
+
const context = contextManager.getContext(contextId);
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
expect(context.id).toBe(contextId);
|
|
20
|
+
expect(context.messages).toEqual([]);
|
|
21
|
+
expect(context.data).toEqual({});
|
|
22
|
+
expect(fs.writeFileSync).toHaveBeenCalled();
|
|
23
|
+
});
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const context = contextManager.getContext(contextId); // Retrieve it
|
|
32
|
-
expect(context.id).toBe(contextId);
|
|
33
|
-
expect(context.messages).toEqual([]);
|
|
34
|
-
});
|
|
25
|
+
it('should retrieve an existing context', () => {
|
|
26
|
+
const contextId = 'test-id';
|
|
27
|
+
contextManager.getContext(contextId); // Create it
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
const context = contextManager.getContext(contextId); // Retrieve it
|
|
30
|
+
expect(context.id).toBe(contextId);
|
|
31
|
+
expect(context.messages).toEqual([]);
|
|
32
|
+
});
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
34
|
+
it('should set context data', () => {
|
|
35
|
+
const contextId = 'test-id';
|
|
36
|
+
contextManager.setContextData(contextId, { key: 'value' });
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
id: 'existing-id',
|
|
48
|
-
createdAt: new Date().toISOString(),
|
|
49
|
-
lastActiveAt: new Date().toISOString(),
|
|
50
|
-
data: { foo: 'bar' },
|
|
51
|
-
messages: [{ role: 'user', content: 'hello' }]
|
|
52
|
-
}
|
|
53
|
-
};
|
|
38
|
+
const context = contextManager.getContext(contextId);
|
|
39
|
+
expect(context.data.key).toBe('value');
|
|
40
|
+
});
|
|
54
41
|
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
it('should load contexts from disk on initialization', () => {
|
|
43
|
+
const mockData = {
|
|
44
|
+
'existing-id': {
|
|
45
|
+
id: 'existing-id',
|
|
46
|
+
createdAt: new Date().toISOString(),
|
|
47
|
+
lastActiveAt: new Date().toISOString(),
|
|
48
|
+
data: { foo: 'bar' },
|
|
49
|
+
messages: [{ role: 'user', content: 'hello' }],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
(fs.existsSync as jest.Mock).mockReturnValue(true);
|
|
54
|
+
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockData));
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
expect(context.messages[0].content).toBe('hello');
|
|
64
|
-
});
|
|
56
|
+
const manager = new ContextManager();
|
|
57
|
+
const context = manager.getContext('existing-id');
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
expect(context.id).toBe('existing-id');
|
|
60
|
+
expect(context.data.foo).toBe('bar');
|
|
61
|
+
expect(context.messages[0].content).toBe('hello');
|
|
62
|
+
});
|
|
70
63
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
it('should clear a context', () => {
|
|
65
|
+
const contextId = 'test-id';
|
|
66
|
+
contextManager.getContext(contextId);
|
|
67
|
+
expect(contextManager.hasContext(contextId)).toBe(true);
|
|
68
|
+
|
|
69
|
+
contextManager.clearContext(contextId);
|
|
70
|
+
expect(contextManager.hasContext(contextId)).toBe(false);
|
|
71
|
+
expect(fs.writeFileSync).toHaveBeenCalled();
|
|
72
|
+
});
|
|
75
73
|
});
|
package/src/context.ts
CHANGED
|
@@ -1,130 +1,139 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
|
|
4
|
+
export interface GibiContextData {
|
|
5
|
+
model?: string;
|
|
6
|
+
agent?: string;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
mode?: string;
|
|
9
|
+
[key: string]: unknown; // Keep any for flexibility but typed common fields
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
export interface GibiContext {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
id: string;
|
|
14
|
+
createdAt: Date;
|
|
15
|
+
lastActiveAt: Date;
|
|
16
|
+
data: GibiContextData;
|
|
17
|
+
messages: Array<{ role: 'user' | 'assistant'; content: string }>;
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
export class ContextManager {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
private contexts: Map<string, GibiContext>;
|
|
22
|
+
private readonly storagePath: string;
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
this.contexts = new Map();
|
|
26
|
+
this.storagePath = path.join(process.cwd(), '.context.json');
|
|
27
|
+
this.load();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private load(): void {
|
|
31
|
+
try {
|
|
32
|
+
if (fs.existsSync(this.storagePath)) {
|
|
33
|
+
const rawData = fs.readFileSync(this.storagePath, 'utf8');
|
|
34
|
+
const parsed = JSON.parse(rawData);
|
|
35
|
+
|
|
36
|
+
// Convert plain objects back to GibiContext with Dates
|
|
37
|
+
Object.values(parsed).forEach((c: unknown) => {
|
|
38
|
+
const ctx = c as any;
|
|
39
|
+
this.contexts.set(ctx.id, {
|
|
40
|
+
...ctx,
|
|
41
|
+
createdAt: new Date(ctx.createdAt),
|
|
42
|
+
lastActiveAt: new Date(ctx.lastActiveAt),
|
|
43
|
+
messages: ctx.messages || [], // Ensure messages exists for old contexts
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(`[ContextManager] Loaded ${this.contexts.size} contexts from disk`);
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('[ContextManager] Failed to load contexts:', error);
|
|
43
51
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private save(): void {
|
|
55
|
+
try {
|
|
56
|
+
// Convert Map to object for JSON serialization
|
|
57
|
+
const data: Record<string, GibiContext> = {};
|
|
58
|
+
this.contexts.forEach((value, key) => {
|
|
59
|
+
data[key] = value;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(this.storagePath, JSON.stringify(data, null, 2));
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('[ContextManager] Failed to save contexts:', error);
|
|
57
65
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Checks if a context exists for the given ID.
|
|
70
|
+
* @param id The context ID.
|
|
71
|
+
*/
|
|
72
|
+
hasContext(id: string): boolean {
|
|
73
|
+
return this.contexts.has(id);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Retrieves or creates a context for the given ID.
|
|
78
|
+
* @param id The context ID (e.g., channel ID or thread timestamp).
|
|
79
|
+
*/
|
|
80
|
+
getContext(id: string): GibiContext {
|
|
81
|
+
let context = this.contexts.get(id);
|
|
82
|
+
let shouldSave = false;
|
|
83
|
+
|
|
84
|
+
if (!context) {
|
|
85
|
+
context = {
|
|
86
|
+
id,
|
|
87
|
+
createdAt: new Date(),
|
|
88
|
+
lastActiveAt: new Date(),
|
|
89
|
+
data: {},
|
|
90
|
+
messages: [],
|
|
91
|
+
};
|
|
92
|
+
this.contexts.set(id, context);
|
|
93
|
+
console.log(`[ContextManager] Created new context: ${id}`);
|
|
94
|
+
shouldSave = true;
|
|
95
|
+
} else {
|
|
96
|
+
console.log(`[ContextManager] Retrieved existing context: ${id}`);
|
|
65
97
|
}
|
|
66
98
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*/
|
|
71
|
-
getContext(id: string): GibiContext {
|
|
72
|
-
let context = this.contexts.get(id);
|
|
73
|
-
let shouldSave = false;
|
|
74
|
-
|
|
75
|
-
if (!context) {
|
|
76
|
-
context = {
|
|
77
|
-
id,
|
|
78
|
-
createdAt: new Date(),
|
|
79
|
-
lastActiveAt: new Date(),
|
|
80
|
-
data: {},
|
|
81
|
-
messages: []
|
|
82
|
-
};
|
|
83
|
-
this.contexts.set(id, context);
|
|
84
|
-
console.log(`[ContextManager] Created new context: ${id}`);
|
|
85
|
-
shouldSave = true;
|
|
86
|
-
} else {
|
|
87
|
-
console.log(`[ContextManager] Retrieved existing context: ${id}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Update activity timestamp
|
|
91
|
-
context.lastActiveAt = new Date();
|
|
92
|
-
shouldSave = true; // Always save on access to update timestamp
|
|
93
|
-
|
|
94
|
-
if (shouldSave) {
|
|
95
|
-
this.save();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return context;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Sets arbitrary data on a context.
|
|
103
|
-
* @param id The context ID.
|
|
104
|
-
* @param data Key-value pairs to merge into the context data.
|
|
105
|
-
*/
|
|
106
|
-
setContextData(id: string, data: Record<string, any>): void {
|
|
107
|
-
const context = this.getContext(id);
|
|
108
|
-
context.data = { ...context.data, ...data };
|
|
109
|
-
this.contexts.set(id, context);
|
|
110
|
-
this.save();
|
|
111
|
-
}
|
|
99
|
+
// Update activity timestamp
|
|
100
|
+
context.lastActiveAt = new Date();
|
|
101
|
+
shouldSave = true; // Always save on access to update timestamp
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*/
|
|
116
|
-
getAllContexts(): GibiContext[] {
|
|
117
|
-
return Array.from(this.contexts.values());
|
|
103
|
+
if (shouldSave) {
|
|
104
|
+
this.save();
|
|
118
105
|
}
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
107
|
+
return context;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sets arbitrary data on a context.
|
|
112
|
+
* @param id The context ID.
|
|
113
|
+
* @param data Key-value pairs to merge into the context data.
|
|
114
|
+
*/
|
|
115
|
+
setContextData(id: string, data: Partial<GibiContextData>): void {
|
|
116
|
+
const context = this.getContext(id);
|
|
117
|
+
context.data = { ...context.data, ...data };
|
|
118
|
+
this.contexts.set(id, context);
|
|
119
|
+
this.save();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Retrieves all stored contexts.
|
|
124
|
+
*/
|
|
125
|
+
getAllContexts(): GibiContext[] {
|
|
126
|
+
return Array.from(this.contexts.values());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Clears a specific context.
|
|
131
|
+
* @param id The context ID to remove.
|
|
132
|
+
*/
|
|
133
|
+
clearContext(id: string): void {
|
|
134
|
+
if (this.contexts.delete(id)) {
|
|
135
|
+
console.log(`[ContextManager] Cleared context: ${id}`);
|
|
136
|
+
this.save();
|
|
129
137
|
}
|
|
138
|
+
}
|
|
130
139
|
}
|
package/test_gemini.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
|
|
3
|
-
console.log(
|
|
4
|
-
const child = spawn(
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
console.log('Starting gemini process...');
|
|
4
|
+
const child = spawn(
|
|
5
|
+
'gemini',
|
|
6
|
+
['list files in current directory', '--output-format', 'stream-json', '--approval-mode', 'yolo'],
|
|
7
|
+
{
|
|
8
|
+
stdio: ['pipe', 'pipe', 'pipe'], // We need to control pipes
|
|
9
|
+
},
|
|
10
|
+
);
|
|
7
11
|
|
|
8
12
|
child.stdout.on('data', (data) => {
|
|
9
|
-
|
|
13
|
+
console.log(`STDOUT: ${data.toString()}`);
|
|
10
14
|
});
|
|
11
15
|
|
|
12
16
|
child.stderr.on('data', (data) => {
|
|
13
|
-
|
|
17
|
+
console.log(`STDERR: ${data.toString()}`);
|
|
14
18
|
});
|
|
15
19
|
|
|
16
20
|
child.on('close', (code) => {
|
|
17
|
-
|
|
21
|
+
console.log(`Child exited with code ${code}`);
|
|
18
22
|
});
|
|
19
23
|
|
|
20
24
|
setTimeout(() => {
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
console.log('Timeout reached. Killing process.');
|
|
26
|
+
child.kill();
|
|
23
27
|
}, 10000);
|
package/test_gemini_approval.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
|
|
3
|
-
console.log(
|
|
3
|
+
console.log('Starting gemini process...');
|
|
4
4
|
// Attempt to force approval mode
|
|
5
|
-
const child = spawn(
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const child = spawn(
|
|
6
|
+
'gemini',
|
|
7
|
+
['list files', '--output-format', 'stream-json', '--approval-mode', 'yolo'],
|
|
8
|
+
{
|
|
9
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
10
|
+
},
|
|
11
|
+
);
|
|
8
12
|
|
|
9
13
|
child.stdout.on('data', (data) => {
|
|
10
|
-
|
|
14
|
+
console.log(`STDOUT: ${data.toString()}`);
|
|
11
15
|
});
|
|
12
16
|
|
|
13
17
|
child.stderr.on('data', (data) => {
|
|
14
|
-
|
|
18
|
+
console.log(`STDERR: ${data.toString()}`);
|
|
15
19
|
});
|
|
16
20
|
|
|
17
21
|
child.on('close', (code) => {
|
|
18
|
-
|
|
22
|
+
console.log(`Child exited with code ${code}`);
|
|
19
23
|
});
|
|
20
24
|
|
|
21
25
|
setTimeout(() => {
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
console.log('Timeout reached. Killing process.');
|
|
27
|
+
child.kill();
|
|
24
28
|
}, 10000);
|
package/test_gemini_write.js
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
|
|
3
|
-
console.log(
|
|
4
|
-
const child = spawn(
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
console.log('Starting gemini process...');
|
|
4
|
+
const child = spawn(
|
|
5
|
+
'gemini',
|
|
6
|
+
[
|
|
7
|
+
'create a file named hello_test_123.txt with content "hello"',
|
|
8
|
+
'--output-format',
|
|
9
|
+
'stream-json',
|
|
10
|
+
'--approval-mode',
|
|
11
|
+
'yolo',
|
|
12
|
+
],
|
|
13
|
+
{
|
|
14
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
15
|
+
},
|
|
16
|
+
);
|
|
7
17
|
|
|
8
18
|
child.stdout.on('data', (data) => {
|
|
9
|
-
|
|
19
|
+
console.log(`STDOUT: ${data.toString()}`);
|
|
10
20
|
});
|
|
11
21
|
|
|
12
22
|
child.stderr.on('data', (data) => {
|
|
13
|
-
|
|
23
|
+
console.log(`STDERR: ${data.toString()}`);
|
|
14
24
|
});
|
|
15
25
|
|
|
16
26
|
child.on('close', (code) => {
|
|
17
|
-
|
|
27
|
+
console.log(`Child exited with code ${code}`);
|
|
18
28
|
});
|
|
19
29
|
|
|
20
30
|
setTimeout(() => {
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
console.log('Timeout reached. Killing process.');
|
|
32
|
+
child.kill();
|
|
23
33
|
}, 15000);
|