@factory/droid-sdk 0.2.0 → 0.4.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.
Files changed (41) hide show
  1. package/README.md +214 -82
  2. package/dist/index.cjs +2345 -692
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +2570 -232
  5. package/dist/index.d.ts +2570 -232
  6. package/dist/index.js +2298 -697
  7. package/dist/index.js.map +1 -1
  8. package/docs/daemon-usage-guide.md +544 -0
  9. package/docs/sdk-usage-guide.md +748 -0
  10. package/examples/abort-session-stream.ts +23 -56
  11. package/examples/daemon-multi-session.ts +67 -0
  12. package/examples/droid-dev-structured-output.ts +5 -5
  13. package/examples/fork-session.ts +33 -7
  14. package/examples/hook-execution.ts +68 -0
  15. package/examples/init-metadata.ts +14 -66
  16. package/examples/interrupt-session.ts +23 -55
  17. package/examples/list-sessions.ts +9 -55
  18. package/examples/multi-turn-session.ts +27 -54
  19. package/examples/permission-handler.ts +27 -84
  20. package/examples/readme-structured-output.ts +26 -0
  21. package/examples/result-metadata.ts +2 -1
  22. package/examples/run.ts +2 -1
  23. package/examples/sdk-mcp-tool.ts +14 -8
  24. package/examples/session-stream.ts +57 -0
  25. package/examples/spec-mode-new-session.ts +39 -99
  26. package/examples/spec-mode-same-session.ts +50 -111
  27. package/examples/structured-output.ts +17 -10
  28. package/examples/test-compact.ts +9 -4
  29. package/examples/test-rewind.ts +49 -117
  30. package/examples/tool-controls.ts +23 -55
  31. package/package.json +3 -1
  32. package/docs/examples/init-metadata.md +0 -79
  33. package/docs/examples/list-sessions.md +0 -64
  34. package/docs/examples/multi-turn-session.md +0 -77
  35. package/docs/examples/one-shot-query.md +0 -73
  36. package/docs/examples/permission-handler.md +0 -102
  37. package/docs/examples/run.md +0 -52
  38. package/docs/examples/spec-mode.md +0 -101
  39. package/docs/examples/tool-controls.md +0 -90
  40. package/docs/typescript-sdk-reference.md +0 -372
  41. package/examples/simple-query.ts +0 -53
@@ -6,43 +6,50 @@
6
6
  *
7
7
  * Usage:
8
8
  * npx tsx examples/structured-output.ts
9
- * npx tsx examples/structured-output.ts "Return a TypeScript pioneer"
9
+ * npx tsx examples/structured-output.ts "Pick a favorite number between 1 and 42"
10
10
  */
11
11
 
12
12
  import assert from 'node:assert/strict';
13
13
 
14
- import { OutputFormatType, run } from '../src/index.js';
14
+ import { OutputFormatType, run } from '@factory/droid-sdk';
15
+
16
+ type FavoriteNumber = { favoriteNumber: number };
15
17
 
16
18
  async function main(): Promise<void> {
17
19
  const prompt =
18
20
  process.argv.slice(2).join(' ') ||
19
- 'Return exactly this person: name Ada Lovelace, language TypeScript.';
21
+ 'Pick a favorite number between 1 and 42.';
20
22
 
21
23
  const outputFormat = {
22
24
  type: OutputFormatType.JsonSchema,
23
25
  schema: {
24
26
  type: 'object',
25
27
  properties: {
26
- name: { type: 'string' },
27
- language: { type: 'string' },
28
+ favoriteNumber: {
29
+ type: 'number',
30
+ minimum: 1,
31
+ maximum: 42,
32
+ },
28
33
  },
29
- required: ['name', 'language'],
34
+ required: ['favoriteNumber'],
30
35
  },
31
36
  };
32
37
 
33
38
  console.log(`Sending prompt: "${prompt}"\n`);
34
39
 
35
40
  const result = await run(prompt, {
41
+ apiKey: process.env.FACTORY_API_KEY!,
36
42
  cwd: process.cwd(),
37
43
  outputFormat,
38
44
  });
39
45
 
40
- assert.ok(result.structuredOutput, 'Expected structuredOutput to be set');
41
- assert.equal(typeof result.structuredOutput['name'], 'string');
42
- assert.equal(typeof result.structuredOutput['language'], 'string');
46
+ const structuredOutput = result.structuredOutput as FavoriteNumber | null;
47
+
48
+ assert.ok(structuredOutput, 'Expected structuredOutput to be set');
49
+ assert.equal(typeof structuredOutput.favoriteNumber, 'number');
43
50
 
44
51
  console.log('=== Structured output ===');
45
- console.log(JSON.stringify(result.structuredOutput, null, 2));
52
+ console.log(JSON.stringify(structuredOutput, null, 2));
46
53
 
47
54
  console.log('\nStructured output example passed');
48
55
  }
@@ -10,10 +10,13 @@
10
10
  * npx tsx examples/test-compact.ts
11
11
  */
12
12
 
13
- import { createSession, DroidMessageType } from '../src/index.js';
13
+ import { createSession, DroidMessageType } from '@factory/droid-sdk';
14
14
 
15
15
  async function main(): Promise<void> {
16
- const session = await createSession({ cwd: process.cwd() });
16
+ const session = await createSession({
17
+ apiKey: process.env.FACTORY_API_KEY!,
18
+ cwd: process.cwd(),
19
+ });
17
20
  try {
18
21
  console.log(`Session created: ${session.sessionId}\n`);
19
22
 
@@ -27,11 +30,13 @@ async function main(): Promise<void> {
27
30
  console.log(`=== Turn ${i + 1} ===`);
28
31
  console.log(`Prompt: "${prompts[i]}"\n`);
29
32
 
30
- for await (const msg of session.stream(prompts[i])) {
33
+ for await (const msg of session.stream(prompts[i], {
34
+ includePartialMessages: true,
35
+ })) {
31
36
  if (msg.type === DroidMessageType.AssistantTextDelta) {
32
37
  process.stdout.write(msg.text);
33
38
  }
34
- if (msg.type === DroidMessageType.TurnComplete) {
39
+ if (msg.type === DroidMessageType.Result) {
35
40
  console.log('\n');
36
41
  }
37
42
  }
@@ -1,155 +1,87 @@
1
1
  /**
2
- * Rewind example (getRewindInfo + executeRewind).
3
- *
4
- * Demonstrates the low-level DroidClient API for rewind operations.
5
- * Uses explicit user message IDs so they can be referenced as rewind
6
- * targets — the snapshot system uses user message IDs as boundary
7
- * markers.
2
+ * Low-level rewind API example.
8
3
  *
9
4
  * Usage:
10
5
  * npx tsx examples/test-rewind.ts
11
6
  */
12
7
 
13
- import crypto from 'node:crypto';
8
+ import { randomUUID } from 'node:crypto';
14
9
 
15
10
  import {
16
- DroidClient,
17
- ProcessTransport,
18
11
  AutonomyLevel,
12
+ DroidClient,
19
13
  DroidWorkingState,
14
+ ProcessTransport,
20
15
  SessionNotificationType,
21
- ToolConfirmationOutcome,
22
- } from '../src/index.js';
23
-
24
- function uuid(): string {
25
- return crypto.randomUUID();
26
- }
27
-
28
- /**
29
- * Send a user message with an explicit messageId and wait for the
30
- * agent turn to complete by observing working-state notifications.
31
- */
32
- async function sendAndWait(
33
- client: DroidClient,
34
- text: string,
35
- messageId: string
36
- ): Promise<void> {
37
- return new Promise<void>((resolve) => {
38
- let wasActive = false;
39
-
40
- const unsub = client.onNotification((notification) => {
41
- const params = notification['params'] as
42
- | Record<string, unknown>
43
- | undefined;
44
- const inner = params?.['notification'] as
45
- | Record<string, unknown>
46
- | undefined;
47
- if (!inner) return;
16
+ } from '@factory/droid-sdk';
17
+
18
+ function waitForTurn(client: DroidClient): Promise<void> {
19
+ return new Promise((resolve) => {
20
+ let started = false;
21
+ const unsubscribe = client.onNotification((notification) => {
22
+ const params = notification['params'];
23
+ if (typeof params !== 'object' || params === null) {
24
+ return;
25
+ }
48
26
 
49
- if (inner['type'] === SessionNotificationType.ASSISTANT_TEXT_DELTA) {
50
- const delta = inner['textDelta'] as string | undefined;
51
- if (delta) process.stdout.write(delta);
27
+ const event = (params as Record<string, unknown>)['notification'];
28
+ if (typeof event !== 'object' || event === null) {
29
+ return;
52
30
  }
53
31
 
54
32
  if (
55
- inner['type'] === SessionNotificationType.DROID_WORKING_STATE_CHANGED
33
+ (event as Record<string, unknown>)['type'] !==
34
+ SessionNotificationType.DROID_WORKING_STATE_CHANGED
56
35
  ) {
57
- const newState = inner['newState'] as string;
58
- if (newState !== DroidWorkingState.Idle) {
59
- wasActive = true;
60
- }
61
- if (newState === DroidWorkingState.Idle && wasActive) {
62
- unsub();
63
- resolve();
64
- }
36
+ return;
65
37
  }
66
- });
67
38
 
68
- void client.addUserMessage({ text, messageId });
39
+ const newState = (event as Record<string, unknown>)['newState'];
40
+ started ||= newState !== DroidWorkingState.Idle;
41
+ if (started && newState === DroidWorkingState.Idle) {
42
+ unsubscribe();
43
+ resolve();
44
+ }
45
+ });
69
46
  });
70
47
  }
71
48
 
72
- async function main(): Promise<void> {
73
- console.log('=== Creating session ===');
74
- const transport = new ProcessTransport({ cwd: process.cwd() });
75
- await transport.connect();
76
- const client = new DroidClient({ transport });
49
+ const transport = new ProcessTransport({ cwd: process.cwd() });
50
+ await transport.connect();
51
+
52
+ const client = new DroidClient({ transport });
77
53
 
78
- const initResult = await client.initializeSession({
54
+ try {
55
+ await client.initializeSession({
79
56
  machineId: 'default',
80
57
  cwd: process.cwd(),
81
58
  autonomyLevel: AutonomyLevel.High,
82
59
  });
83
- console.log(`Session ID: ${initResult.sessionId}\n`);
84
-
85
- client.setPermissionHandler(() => ToolConfirmationOutcome.ProceedOnce);
86
-
87
- const userMsgId1 = uuid();
88
- console.log(`=== Turn 1: "hello" (msgId: ${userMsgId1.slice(0, 12)}...) ===`);
89
- await sendAndWait(client, 'Say "hello" and nothing else.', userMsgId1);
90
- console.log('\n');
91
60
 
92
- const userMsgId2 = uuid();
93
- console.log(
94
- `=== Turn 2: create file (msgId: ${userMsgId2.slice(0, 12)}...) ===`
95
- );
96
- await sendAndWait(
97
- client,
98
- 'Create a file called /tmp/droid-sdk-rewind-test.txt with the content ' +
99
- '"rewind test data". Do not explain, just create it.',
100
- userMsgId2
101
- );
102
- console.log('\n');
61
+ const messageId = randomUUID();
62
+ const done = waitForTurn(client);
63
+ await client.addUserMessage({
64
+ messageId,
65
+ text: 'Say "hello" and nothing else.',
66
+ });
67
+ await done;
103
68
 
104
- console.log(`=== getRewindInfo (msgId: ${userMsgId2.slice(0, 12)}...) ===`);
105
69
  try {
106
- const rewindInfo = await client.getRewindInfo({ messageId: userMsgId2 });
107
- console.log(
108
- ` Available files to restore: ${rewindInfo.availableFiles.length}`
109
- );
110
- for (const f of rewindInfo.availableFiles) {
111
- console.log(` - ${f.filePath} (${f.size} bytes)`);
112
- }
113
- console.log(` Created files to delete: ${rewindInfo.createdFiles.length}`);
114
- for (const f of rewindInfo.createdFiles) {
115
- console.log(` - ${f.filePath}`);
116
- }
117
- console.log(` Evicted files: ${rewindInfo.evictedFiles.length}`);
118
- } catch (err) {
70
+ const rewindInfo = await client.getRewindInfo({ messageId });
119
71
  console.log(
120
- ` Error (expected — server does not yet support this): ${err instanceof Error ? err.message : String(err)}`
72
+ `Files available to restore: ${rewindInfo.availableFiles.length}`
121
73
  );
122
- }
123
- console.log();
124
74
 
125
- console.log(
126
- `=== executeRewind (to msgId: ${userMsgId1.slice(0, 12)}...) ===`
127
- );
128
- try {
129
- const rewindResult = await client.executeRewind({
130
- messageId: userMsgId1,
75
+ const rewind = await client.executeRewind({
76
+ messageId,
131
77
  filesToRestore: [],
132
78
  filesToDelete: [],
133
- forkTitle: 'Rewind to first hello',
79
+ forkTitle: 'Rewind example',
134
80
  });
135
- console.log(` New session ID: ${rewindResult.newSessionId}`);
136
- console.log(` Restored: ${rewindResult.restoredCount}`);
137
- console.log(` Deleted: ${rewindResult.deletedCount}`);
138
- console.log(` Failed restores: ${rewindResult.failedRestoreCount}`);
139
- console.log(` Failed deletes: ${rewindResult.failedDeleteCount}`);
140
- } catch (err) {
141
- console.log(
142
- ` Error (expected — server does not yet support this): ${err instanceof Error ? err.message : String(err)}`
143
- );
81
+ console.log(`Rewound into session: ${rewind.newSessionId}`);
82
+ } catch (error) {
83
+ console.log(`Rewind unavailable: ${String(error)}`);
144
84
  }
145
- console.log();
146
-
147
- console.log('=== Closing ===');
85
+ } finally {
148
86
  await client.close();
149
- console.log('Done.');
150
87
  }
151
-
152
- main().catch((err: unknown) => {
153
- console.error('Fatal error:', err);
154
- process.exit(1);
155
- });
@@ -1,69 +1,37 @@
1
1
  /**
2
2
  * Tool controls example.
3
3
  *
4
- * Demonstrates:
5
- * - configuring enabled and disabled tool IDs at session creation
6
- * - inspecting the current tool catalog with `listTools()`
7
- * - updating tool overrides with `updateSettings()`
8
- *
9
4
  * Usage:
10
5
  * npx tsx examples/tool-controls.ts
11
6
  */
12
7
 
13
- import { createSession } from '../src/index.js';
14
- import type { ExecToolInfo } from '../src/index.js';
8
+ import { createSession } from '@factory/droid-sdk';
15
9
 
16
- function printToolState(label: string, tool: ExecToolInfo | undefined): void {
17
- if (!tool) {
18
- console.log(`${label}: not present in tool catalog`);
19
- return;
20
- }
10
+ const session = await createSession({
11
+ apiKey: process.env.FACTORY_API_KEY!,
12
+ cwd: process.cwd(),
13
+ enabledToolIds: ['Read', 'Grep'],
14
+ disabledToolIds: ['Execute'],
15
+ });
21
16
 
17
+ try {
18
+ const before = await session.listTools();
22
19
  console.log(
23
- `${label}: defaultAllowed=${tool.defaultAllowed}, currentlyAllowed=${tool.currentlyAllowed}`
20
+ before.tools
21
+ .filter((tool) => ['Read', 'Execute'].includes(tool.llmId))
22
+ .map((tool) => `${tool.llmId}: ${String(tool.currentlyAllowed)}`)
23
+ .join('\n')
24
24
  );
25
- }
26
-
27
- async function main(): Promise<void> {
28
- const session = await createSession({
29
- cwd: process.cwd(),
30
- enabledToolIds: ['Read', 'Glob', 'Grep'],
31
- disabledToolIds: ['Execute'],
32
- });
33
-
34
- try {
35
- console.log(`Session created: ${session.sessionId}\n`);
36
-
37
- const initial = await session.listTools();
38
- const initialRead = initial.tools.find((tool) => tool.llmId === 'Read');
39
- const initialExecute = initial.tools.find(
40
- (tool) => tool.llmId === 'Execute'
41
- );
42
25
 
43
- console.log('=== Initial tool state ===');
44
- printToolState('Read', initialRead);
45
- printToolState('Execute', initialExecute);
26
+ await session.updateSettings({ disabledToolIds: ['Read', 'Execute'] });
46
27
 
47
- await session.updateSettings({
48
- disabledToolIds: ['Read', 'Execute'],
49
- });
50
-
51
- const updated = await session.listTools();
52
- const updatedRead = updated.tools.find((tool) => tool.llmId === 'Read');
53
- const updatedExecute = updated.tools.find(
54
- (tool) => tool.llmId === 'Execute'
55
- );
56
-
57
- console.log('\n=== Updated tool state ===');
58
- printToolState('Read', updatedRead);
59
- printToolState('Execute', updatedExecute);
60
- } finally {
61
- await session.close();
62
- console.log('\nSession closed.');
63
- }
28
+ const after = await session.listTools();
29
+ console.log(
30
+ after.tools
31
+ .filter((tool) => ['Read', 'Execute'].includes(tool.llmId))
32
+ .map((tool) => `${tool.llmId}: ${String(tool.currentlyAllowed)}`)
33
+ .join('\n')
34
+ );
35
+ } finally {
36
+ await session.close();
64
37
  }
65
-
66
- main().catch((err: unknown) => {
67
- console.error('Error:', err);
68
- process.exit(1);
69
- });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factory/droid-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "TypeScript SDK for the Factory Droid CLI",
6
6
  "keywords": [
@@ -57,12 +57,14 @@
57
57
  "dependencies": {
58
58
  "@modelcontextprotocol/sdk": "^1.29.0",
59
59
  "uuid": "^11.1.0",
60
+ "ws": "^8.21.0",
60
61
  "zod": "^3.24.0"
61
62
  },
62
63
  "devDependencies": {
63
64
  "@eslint/js": "^9.39.4",
64
65
  "@types/node": "^22.15.0",
65
66
  "@types/uuid": "^10.0.0",
67
+ "@types/ws": "^8.18.1",
66
68
  "@vitest/coverage-v8": "^3.1.0",
67
69
  "eslint": "^9.24.0",
68
70
  "eslint-import-resolver-typescript": "^4.4.4",
@@ -1,79 +0,0 @@
1
- # Example: initialization metadata
2
-
3
- Use this when you need the raw initialization payload, early session IDs, or model/settings metadata.
4
-
5
- ## What this example shows
6
-
7
- - reading `query().sessionId` and `query().initResult`
8
- - waiting for `query().initialized`
9
- - reading `session.initResult` from `createSession()` and `resumeSession()`
10
-
11
- ## Key snippet: inspect a query before and after initialization
12
-
13
- ```ts
14
- const stream = query({
15
- prompt: 'Reply with "ready" and nothing else.',
16
- cwd: process.cwd(),
17
- });
18
-
19
- console.log(stream.sessionId);
20
- console.log(stream.initResult);
21
-
22
- const initialized = await stream.initialized;
23
- console.log(initialized.sessionId);
24
- console.log(initialized.settings.modelId);
25
- ```
26
-
27
- Before initialization completes, `sessionId` and `initResult` are `null`.
28
-
29
- ## Key snippet: read metadata from sessions
30
-
31
- ```ts
32
- const session = await createSession({ cwd: process.cwd() });
33
- const resumed = await resumeSession(session.sessionId, { cwd: process.cwd() });
34
-
35
- console.log(session.initResult.settings.modelId);
36
- console.log(resumed.initResult.cwd);
37
- ```
38
-
39
- ## Full script
40
-
41
- ```ts
42
- import { createSession, query, resumeSession } from '@factory/droid-sdk';
43
-
44
- async function main(): Promise<void> {
45
- const stream = query({
46
- prompt: 'Reply with "ready" and nothing else.',
47
- cwd: process.cwd(),
48
- });
49
-
50
- console.log(stream.sessionId);
51
- console.log(stream.initResult);
52
-
53
- const initialized = await stream.initialized;
54
- console.log(initialized.sessionId);
55
- console.log(initialized.settings.modelId);
56
-
57
- stream.abort();
58
-
59
- const session = await createSession({ cwd: process.cwd() });
60
- let resumed: Awaited<ReturnType<typeof resumeSession>> | null = null;
61
-
62
- try {
63
- resumed = await resumeSession(session.sessionId, {
64
- cwd: process.cwd(),
65
- });
66
-
67
- console.log(session.initResult.settings.modelId);
68
- console.log(resumed.initResult.cwd);
69
- } finally {
70
- await resumed?.close();
71
- await session.close();
72
- }
73
- }
74
-
75
- main().catch((err: unknown) => {
76
- console.error('Error:', err);
77
- process.exit(1);
78
- });
79
- ```
@@ -1,64 +0,0 @@
1
- # Example: list saved sessions
2
-
3
- Use `listSessions()` when you need recent Droid session history without launching a subprocess.
4
-
5
- ## What this example shows
6
-
7
- - listing recent sessions for the current project
8
- - listing recent sessions across all projects
9
- - formatting returned `SessionMetadata`
10
-
11
- ## Key snippet: project-scoped listing
12
-
13
- ```ts
14
- const currentProject = await listSessions({ numSessions: 10 });
15
- ```
16
-
17
- ## Key snippet: cross-project listing
18
-
19
- ```ts
20
- const allSessions = await listSessions({
21
- fetchOutsideCWD: true,
22
- numSessions: 5,
23
- });
24
- ```
25
-
26
- This helper reads local session files directly and returns `SessionMetadata[]`.
27
-
28
- ## Full script
29
-
30
- ```ts
31
- import { listSessions } from '@factory/droid-sdk';
32
-
33
- function formatDate(date: Date): string {
34
- return date.toISOString().replace('T', ' ').slice(0, 19);
35
- }
36
-
37
- async function main(): Promise<void> {
38
- const currentProject = await listSessions({ numSessions: 10 });
39
-
40
- for (const session of currentProject) {
41
- const title = session.sessionTitle ?? session.title ?? '(untitled)';
42
- console.log(
43
- `[${session.id.slice(0, 8)}] ${formatDate(session.modifiedTime)} ${session.messageCount} msgs — ${title}`
44
- );
45
- }
46
-
47
- const allSessions = await listSessions({
48
- fetchOutsideCWD: true,
49
- numSessions: 5,
50
- });
51
-
52
- for (const session of allSessions) {
53
- const title = session.sessionTitle ?? session.title ?? '(untitled)';
54
- console.log(
55
- `[${session.id.slice(0, 8)}] ${formatDate(session.modifiedTime)} — ${title}`
56
- );
57
- }
58
- }
59
-
60
- main().catch((err: unknown) => {
61
- console.error('Error:', err);
62
- process.exit(1);
63
- });
64
- ```
@@ -1,77 +0,0 @@
1
- # Example: multi-turn session
2
-
3
- Use `createSession()` when you want conversation state to persist across turns.
4
-
5
- ## What this example shows
6
-
7
- - creating a persistent session
8
- - streaming one turn with `session.stream()`
9
- - collecting a later turn with `session.send()`
10
- - closing the session cleanly
11
-
12
- ## Key snippet: create the session
13
-
14
- ```ts
15
- const session = await createSession({ cwd: process.cwd() });
16
- ```
17
-
18
- Create the session once and reuse it across prompts.
19
-
20
- ## Key snippet: stream a turn
21
-
22
- ```ts
23
- import { DroidMessageType } from '@factory/droid-sdk';
24
-
25
- for await (const msg of session.stream(
26
- 'List the TypeScript files in this project'
27
- )) {
28
- if (msg.type === DroidMessageType.AssistantTextDelta) {
29
- process.stdout.write(msg.text);
30
- }
31
- }
32
- ```
33
-
34
- Use `stream()` when you want incremental output.
35
-
36
- ## Key snippet: collect a full response
37
-
38
- ```ts
39
- const result = await session.send('Summarize the project in one sentence');
40
- console.log(result.text);
41
- ```
42
-
43
- Use `send()` when you want the SDK to aggregate the turn for you.
44
-
45
- ## Full script
46
-
47
- ```ts
48
- import { createSession, DroidMessageType } from '@factory/droid-sdk';
49
-
50
- async function main(): Promise<void> {
51
- const session = await createSession({ cwd: process.cwd() });
52
-
53
- console.log(`Session created: ${session.sessionId}\n`);
54
-
55
- try {
56
- for await (const msg of session.stream(
57
- 'List the TypeScript files in this project'
58
- )) {
59
- if (msg.type === DroidMessageType.AssistantTextDelta) {
60
- process.stdout.write(msg.text);
61
- }
62
- }
63
-
64
- console.log('\n');
65
-
66
- const result = await session.send('Summarize the project in one sentence');
67
- console.log(result.text);
68
- } finally {
69
- await session.close();
70
- }
71
- }
72
-
73
- main().catch((err: unknown) => {
74
- console.error('Error:', err);
75
- process.exit(1);
76
- });
77
- ```