@swarm-agent/sdk 0.1.49 → 0.1.50

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 (46) hide show
  1. package/HEADLESS_UI.md +4 -6
  2. package/README.md +121 -56
  3. package/dist/agents.d.ts +114 -0
  4. package/dist/agents.js +183 -0
  5. package/dist/auth.d.ts +14 -0
  6. package/dist/auth.js +16 -0
  7. package/dist/box.d.ts +68 -0
  8. package/dist/box.js +13 -0
  9. package/dist/browser.d.ts +6 -2
  10. package/dist/browser.js +11 -3
  11. package/dist/chat.d.ts +41 -7
  12. package/dist/chat.js +148 -83
  13. package/dist/client.d.ts +12 -0
  14. package/dist/client.js +19 -6
  15. package/dist/copy-tags.d.ts +26 -0
  16. package/dist/copy-tags.js +133 -0
  17. package/dist/errors.d.ts +16 -0
  18. package/dist/errors.js +21 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.js +6 -0
  21. package/dist/local-connection.d.ts +39 -0
  22. package/dist/local-connection.js +56 -0
  23. package/dist/permission-ui.d.ts +1 -0
  24. package/dist/permission-ui.js +37 -0
  25. package/dist/permissions.d.ts +4 -0
  26. package/dist/permissions.js +7 -0
  27. package/dist/projects.d.ts +25 -3
  28. package/dist/projects.js +65 -6
  29. package/dist/provider-auth.d.ts +5 -3
  30. package/dist/provider-auth.js +6 -8
  31. package/dist/realtime.d.ts +40 -6
  32. package/dist/realtime.js +156 -52
  33. package/dist/remote.d.ts +59 -0
  34. package/dist/remote.js +46 -0
  35. package/dist/sessions.d.ts +31 -1
  36. package/dist/sessions.js +49 -1
  37. package/dist/settings.d.ts +3 -0
  38. package/dist/settings.js +19 -3
  39. package/dist/signals.d.ts +148 -0
  40. package/dist/signals.js +97 -0
  41. package/dist/types.d.ts +70 -2
  42. package/dist/websocket.d.ts +3 -1
  43. package/dist/websocket.js +1 -1
  44. package/dist/workspaces.d.ts +11 -0
  45. package/dist/workspaces.js +17 -0
  46. package/package.json +1 -1
package/dist/settings.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SwarmNotFoundError } from './errors.js';
1
2
  /** Account-scoped settings. Requires the local administrative connection, not a session token. */
2
3
  export class SwarmSettingsNamespace {
3
4
  transport;
@@ -23,12 +24,22 @@ export class SwarmSettingsNamespace {
23
24
  /**
24
25
  * Restores verified recommended defaults across all system agent and swarm core roles.
25
26
  * If expectedUpdatedAt is omitted, automatically reads the current setting's revision first.
27
+ * An account with no model settings yet (a provider is connected but its
28
+ * models were never set up) restores from revision 0: the daemon then sets
29
+ * up the recommended models from a ready provider.
26
30
  */
27
31
  async restoreDefaults(expectedUpdatedAt) {
28
32
  let updatedAt = expectedUpdatedAt;
29
33
  if (typeof updatedAt !== 'number') {
30
- const current = await this.agentModels();
31
- updatedAt = current.agent_model_settings.updated_at;
34
+ try {
35
+ const current = await this.agentModels();
36
+ updatedAt = current.agent_model_settings.updated_at;
37
+ }
38
+ catch (error) {
39
+ if (!(error instanceof SwarmNotFoundError))
40
+ throw error;
41
+ updatedAt = 0;
42
+ }
32
43
  }
33
44
  return (await this.transport.request('/v1/agent-model-settings/restore-defaults', {
34
45
  method: 'POST',
@@ -87,7 +98,12 @@ export class SwarmSettingsNamespace {
87
98
  */
88
99
  async applyProviderFleet(provider) {
89
100
  const fleet = await this.getRecommendationsForProvider(provider);
90
- return this.patch(fleet);
101
+ // The daemon takes one target per PATCH: the swarm pair, then each system agent.
102
+ let result = await this.patch({ swarm: fleet.swarm });
103
+ for (const [slot, assignment] of Object.entries(fleet.system_agents)) {
104
+ result = await this.patch({ system_agents: { [slot]: assignment } });
105
+ }
106
+ return result;
91
107
  }
92
108
  async patch(body) {
93
109
  return (await this.transport.request('/v1/agent-model-settings', { method: 'PATCH', body })).data;
@@ -0,0 +1,148 @@
1
+ import type { SwarmTransport } from './transport.js';
2
+ export type SignalSeverity = 'info' | 'warning' | 'critical';
3
+ /**
4
+ * One entry in a machine's signal feed: a high-level event such as an agent
5
+ * waiting on a person, a failed run, a refused key or an event an outside
6
+ * source reported. Signals carry ids and a one-line summary, never transcripts,
7
+ * command arguments or secret values.
8
+ */
9
+ export interface Signal {
10
+ /** Position in this machine's feed; pass the last one back as `after`. */
11
+ seq: number;
12
+ /** Unique id, stable across forwarding (use it to drop duplicates). */
13
+ id: string;
14
+ /** Unix milliseconds. */
15
+ at: number;
16
+ /** Dotted kind, e.g. `agent.blocked`, `run.failed`, `external.falco`. */
17
+ kind: string;
18
+ severity: SignalSeverity;
19
+ /** `swarmd` for Swarm's own signals, `external:<name>` for reported ones. */
20
+ source: string;
21
+ /** Empty for machine-wide signals. */
22
+ account?: string;
23
+ summary: string;
24
+ /** Signals with the same key describe the same ongoing condition. */
25
+ dedup_key?: string;
26
+ refs?: Record<string, string>;
27
+ attrs?: Record<string, string>;
28
+ }
29
+ export interface SignalPage {
30
+ signals: Signal[];
31
+ /** The last position examined; pass it as `after` to continue. */
32
+ next_after: number;
33
+ oldest_seq: number;
34
+ latest_seq: number;
35
+ /**
36
+ * True when signals after your cursor were already dropped (the feed keeps
37
+ * the newest entries only) or your cursor is ahead of the feed. Resync
38
+ * from `next_after` and treat the missed range as unknown.
39
+ */
40
+ gap: boolean;
41
+ }
42
+ export interface ListSignalsParams {
43
+ /** Return signals numbered after this position (default 0: from the oldest kept). */
44
+ after?: number;
45
+ /** At most this many signals (default 100, maximum 500). */
46
+ limit?: number;
47
+ /** Kind prefixes, e.g. `['agent', 'run.failed']`. */
48
+ kinds?: string[];
49
+ /** Only signals at least this severe. */
50
+ minSeverity?: SignalSeverity;
51
+ }
52
+ export interface ReportSignalParams {
53
+ /** Must start with `external.`, e.g. `external.falco`. */
54
+ kind: string;
55
+ /** One line; longer text is cut to 300 characters. */
56
+ summary: string;
57
+ severity?: SignalSeverity;
58
+ /** Lowercase name of the reporting source, e.g. `falco`. */
59
+ source?: string;
60
+ dedup_key?: string;
61
+ refs?: Record<string, string>;
62
+ attrs?: Record<string, string>;
63
+ }
64
+ /**
65
+ * The machine signal feed. Reading needs a key with `signals:read`; reporting
66
+ * an outside event needs `signals:write`. Readers see their own account's
67
+ * signals and machine-wide ones.
68
+ */
69
+ export declare class SwarmSignalsNamespace {
70
+ private readonly transport;
71
+ /** Where this machine forwards its feed (owner only). */
72
+ readonly sinks: SwarmSignalSinksNamespace;
73
+ constructor(transport: SwarmTransport);
74
+ /** Lists signals after a cursor, oldest first. */
75
+ list(params?: ListSignalsParams): Promise<SignalPage>;
76
+ /**
77
+ * Reports an outside event (a host security monitor, a backup script) into
78
+ * the feed. Swarm records the source as `external:<source>` and, for a
79
+ * scoped key, which key reported it.
80
+ */
81
+ report(params: ReportSignalParams): Promise<Signal>;
82
+ }
83
+ /** Where a machine forwards its signal feed (owner only). */
84
+ export interface SignalSink {
85
+ id: string;
86
+ account: string;
87
+ name: string;
88
+ url: string;
89
+ kinds?: string[];
90
+ min_severity?: SignalSeverity;
91
+ heartbeat_seconds: number;
92
+ /** Last signal position the sink accepted. */
93
+ cursor: number;
94
+ created_at: number;
95
+ last_delivered_at?: number;
96
+ last_attempt_at?: number;
97
+ last_error?: string;
98
+ failures?: number;
99
+ }
100
+ export interface CreateSignalSinkParams {
101
+ name: string;
102
+ /** https URL (http only to this machine's loopback). */
103
+ url: string;
104
+ /** Kind prefixes to forward, e.g. `['agent', 'worker.run.failed']`; empty forwards all. */
105
+ kinds?: string[];
106
+ minSeverity?: SignalSeverity;
107
+ /** Idle heartbeat interval, 30 to 3600 seconds (default 60). */
108
+ heartbeatSeconds?: number;
109
+ }
110
+ /**
111
+ * Signal sinks: the machine POSTs batches of its feed to each sink, signed
112
+ * with the sink's secret (see `verifySignalDelivery`). Owner only: scoped
113
+ * keys, including admin keys, are refused.
114
+ */
115
+ export declare class SwarmSignalSinksNamespace {
116
+ private readonly transport;
117
+ constructor(transport: SwarmTransport);
118
+ list(): Promise<SignalSink[]>;
119
+ /** Creates a sink. The signing secret is returned only here; store it in the receiver. */
120
+ create(params: CreateSignalSinkParams): Promise<{
121
+ sink: SignalSink;
122
+ secret: string;
123
+ }>;
124
+ delete(id: string): Promise<void>;
125
+ }
126
+ /** Body of one signal delivery (version 1). */
127
+ export interface SignalDelivery {
128
+ version: 1;
129
+ sink_id: string;
130
+ machine?: string;
131
+ sent_at: number;
132
+ /** True for an idle heartbeat with no signals. */
133
+ heartbeat: boolean;
134
+ signals: Signal[];
135
+ next_after: number;
136
+ latest_seq: number;
137
+ gap: boolean;
138
+ }
139
+ /**
140
+ * Checks a delivery's `X-Swarm-Signature` for a receiver: HMAC-SHA256 of
141
+ * `${X-Swarm-Timestamp}.${rawBody}` with the sink secret, and a timestamp
142
+ * within `maxSkewMs` of now (rejects replays). Pass the raw request body,
143
+ * not re-serialized JSON. Works with WebCrypto (Node 18+, Workers, browsers).
144
+ */
145
+ export declare function verifySignalDelivery(secret: string, timestamp: string | number, rawBody: string | Uint8Array, signature: string, { now, maxSkewMs }?: {
146
+ now?: number;
147
+ maxSkewMs?: number;
148
+ }): Promise<boolean>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The machine signal feed. Reading needs a key with `signals:read`; reporting
3
+ * an outside event needs `signals:write`. Readers see their own account's
4
+ * signals and machine-wide ones.
5
+ */
6
+ export class SwarmSignalsNamespace {
7
+ transport;
8
+ /** Where this machine forwards its feed (owner only). */
9
+ sinks;
10
+ constructor(transport) {
11
+ this.transport = transport;
12
+ this.sinks = new SwarmSignalSinksNamespace(transport);
13
+ }
14
+ /** Lists signals after a cursor, oldest first. */
15
+ async list(params = {}) {
16
+ const q = new URLSearchParams();
17
+ if (typeof params.after === 'number')
18
+ q.set('after', String(params.after));
19
+ if (typeof params.limit === 'number')
20
+ q.set('limit', String(params.limit));
21
+ if (params.kinds?.length)
22
+ q.set('kind', params.kinds.join(','));
23
+ if (params.minSeverity)
24
+ q.set('min_severity', params.minSeverity);
25
+ const path = q.toString() ? `/v3/signals?${q.toString()}` : '/v3/signals';
26
+ const res = await this.transport.request(path, { method: 'GET' });
27
+ const { signals, next_after, oldest_seq, latest_seq, gap } = res.data;
28
+ return { signals: signals ?? [], next_after, oldest_seq, latest_seq, gap };
29
+ }
30
+ /**
31
+ * Reports an outside event (a host security monitor, a backup script) into
32
+ * the feed. Swarm records the source as `external:<source>` and, for a
33
+ * scoped key, which key reported it.
34
+ */
35
+ async report(params) {
36
+ const res = await this.transport.request('/v3/signals', {
37
+ method: 'POST',
38
+ body: params,
39
+ });
40
+ return res.data.signal;
41
+ }
42
+ }
43
+ /**
44
+ * Signal sinks: the machine POSTs batches of its feed to each sink, signed
45
+ * with the sink's secret (see `verifySignalDelivery`). Owner only: scoped
46
+ * keys, including admin keys, are refused.
47
+ */
48
+ export class SwarmSignalSinksNamespace {
49
+ transport;
50
+ constructor(transport) {
51
+ this.transport = transport;
52
+ }
53
+ async list() {
54
+ const res = await this.transport.request('/v3/signals/sinks', { method: 'GET' });
55
+ return res.data.sinks ?? [];
56
+ }
57
+ /** Creates a sink. The signing secret is returned only here; store it in the receiver. */
58
+ async create(params) {
59
+ const res = await this.transport.request('/v3/signals/sinks', {
60
+ method: 'POST',
61
+ body: {
62
+ name: params.name,
63
+ url: params.url,
64
+ kinds: params.kinds,
65
+ min_severity: params.minSeverity,
66
+ heartbeat_seconds: params.heartbeatSeconds,
67
+ },
68
+ });
69
+ return { sink: res.data.sink, secret: res.data.secret };
70
+ }
71
+ async delete(id) {
72
+ await this.transport.request(`/v3/signals/sinks/${encodeURIComponent(id)}`, { method: 'DELETE' });
73
+ }
74
+ }
75
+ /**
76
+ * Checks a delivery's `X-Swarm-Signature` for a receiver: HMAC-SHA256 of
77
+ * `${X-Swarm-Timestamp}.${rawBody}` with the sink secret, and a timestamp
78
+ * within `maxSkewMs` of now (rejects replays). Pass the raw request body,
79
+ * not re-serialized JSON. Works with WebCrypto (Node 18+, Workers, browsers).
80
+ */
81
+ export async function verifySignalDelivery(secret, timestamp, rawBody, signature, { now = Date.now(), maxSkewMs = 5 * 60 * 1000 } = {}) {
82
+ const ts = Number(timestamp);
83
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewMs || !signature.startsWith('v1='))
84
+ return false;
85
+ const enc = new TextEncoder();
86
+ const body = typeof rawBody === 'string' ? enc.encode(rawBody) : rawBody;
87
+ const message = new Uint8Array([...enc.encode(`${ts}.`), ...body]);
88
+ const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
89
+ const mac = new Uint8Array(await crypto.subtle.sign('HMAC', key, message));
90
+ const expected = 'v1=' + Array.from(mac, (b) => b.toString(16).padStart(2, '0')).join('');
91
+ if (expected.length !== signature.length)
92
+ return false;
93
+ let diff = 0;
94
+ for (let i = 0; i < expected.length; i++)
95
+ diff |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
96
+ return diff === 0;
97
+ }
package/dist/types.d.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  export type TokenScope = 'automations:trigger' | 'automations:read' | 'automations:write' | 'sessions:read' | 'sessions:write' | 'projects:read' | 'projects:write' | 'admin' | string;
2
2
  export interface SwarmClientConfig {
3
- /** Base URL for the Swarm API/Desktop daemon (e.g. http://127.0.0.1:18080 or http://127.0.0.1:7781) */
3
+ /**
4
+ * Base URL of the Swarm daemon API (default http://127.0.0.1:7781). With no
5
+ * `baseUrl`, `socketPath` or environment override, Node clients use the
6
+ * owner socket of a system install when this process may connect to it.
7
+ */
4
8
  baseUrl?: string;
5
9
  /** Bearer authentication token (attach token or scoped token starting with 'swk_') */
6
10
  token?: string;
7
- /** Unix domain socket path for zero-conf local IPC (e.g. /run/swarmd/swarmd.sock) */
11
+ /** Unix socket for local IPC (owner socket: /var/lib/swarmd/local-transport/api.sock) */
8
12
  socketPath?: string;
9
13
  /** Custom default HTTP headers */
10
14
  defaultHeaders?: Record<string, string>;
@@ -535,6 +539,43 @@ export interface ProjectTaskPlanBinding {
535
539
  session_id?: string;
536
540
  receipt?: string;
537
541
  }
542
+ /**
543
+ * Backend-inspected delivery facts for a task's captured worktree lane. Only the
544
+ * task-detail GET (`getTask`) observes Git; collection rows carry `freshness: 'stale'`.
545
+ */
546
+ export interface ProjectTaskDeliveryAssessment {
547
+ task_id?: string;
548
+ task_revision?: number;
549
+ attempt_id?: string;
550
+ session_id?: string;
551
+ base_oid?: string;
552
+ source_oid?: string;
553
+ target_oid?: string;
554
+ source_branch?: string;
555
+ target_branch?: string;
556
+ state?: 'candidate_work' | 'integrated' | 'unavailable' | string;
557
+ reason_code?: string;
558
+ reason?: string;
559
+ freshness?: 'observed' | 'stale' | 'unknown' | string;
560
+ source_dirty?: boolean;
561
+ target_dirty?: boolean;
562
+ candidate_commits?: number;
563
+ files?: string[];
564
+ allowed_actions?: string[];
565
+ [key: string]: unknown;
566
+ }
567
+ /** One coordinator attempt (session reservation) on a project task. */
568
+ export interface ProjectTaskAttempt {
569
+ id: string;
570
+ session_id?: string;
571
+ status?: string;
572
+ role?: string;
573
+ workspace_path?: string;
574
+ worktree_branch?: string;
575
+ base_branch?: string;
576
+ base_commit?: string;
577
+ [key: string]: unknown;
578
+ }
538
579
  export interface ProjectTaskRecord {
539
580
  id: string;
540
581
  project_id: string;
@@ -543,6 +584,9 @@ export interface ProjectTaskRecord {
543
584
  description?: string;
544
585
  status: 'queued' | 'in_progress' | 'needs_review' | 'completed' | 'failed' | 'pending_approval' | 'planning' | 'rejected' | string;
545
586
  session_id?: string;
587
+ active_attempt_id?: string;
588
+ attempts?: ProjectTaskAttempt[];
589
+ delivery_assessment?: ProjectTaskDeliveryAssessment;
546
590
  agent?: string;
547
591
  worker_name?: string;
548
592
  outcome_type?: string;
@@ -744,6 +788,30 @@ export interface RefineProjectTaskResult {
744
788
  status: 'refined' | string;
745
789
  task: ProjectTaskRecord;
746
790
  }
791
+ /**
792
+ * Selection body for POST /v3/projects/{id}/tasks/{taskId}/integrate. The daemon
793
+ * requires `session_id` (must equal the task's current session) plus the captured
794
+ * `source_branch`/`target_branch` (must equal the session worktree branch and its
795
+ * base branch). The remaining fields are the stale-state guards the daemon's
796
+ * `recover-integrate` path checks; plain integrate accepts and ignores them.
797
+ */
798
+ export interface IntegrateProjectTaskSelection {
799
+ session_id: string;
800
+ source_branch: string;
801
+ target_branch: string;
802
+ revision?: number;
803
+ attempt_id?: string;
804
+ source_head?: string;
805
+ target_head?: string;
806
+ }
807
+ export interface IntegrateProjectTaskOptions extends RequestOptions {
808
+ /**
809
+ * Overrides merged over the selection the SDK derives from a fresh `getTask`.
810
+ * When `session_id`, `source_branch` and `target_branch` are all supplied, the
811
+ * SDK skips the task fetch and sends them as given.
812
+ */
813
+ selection?: Partial<IntegrateProjectTaskSelection>;
814
+ }
747
815
  export interface IntegrateProjectTaskResult {
748
816
  status: 'integrated' | 'already_integrated' | string;
749
817
  message?: string;
@@ -3,7 +3,7 @@ import type { Server as HttpsServer } from 'node:https';
3
3
  import { WebSocket } from 'ws';
4
4
  import type { SwarmChatNamespace, ToolStreamItem } from './chat.js';
5
5
  import type { PendingPermissionRecord, SwarmPermissionsNamespace, PermissionResolver, ResolvePermissionOptions, ResolvePermissionResult } from './permissions.js';
6
- import type { SessionWatchState, V3Message } from './realtime.js';
6
+ import type { SessionWatchState, V3Message, V3RunState } from './realtime.js';
7
7
  export interface SwarmWebSocketBridgeOptions {
8
8
  path?: string;
9
9
  /** Server-owned policy. Browser frames cannot enable auto approval. Questions always require a reply. */
@@ -37,6 +37,8 @@ export interface SwarmWebSocketServerMessage {
37
37
  result?: ResolvePermissionResult;
38
38
  permissionId?: string;
39
39
  messages?: V3Message[];
40
+ /** Finished run on `done`: `status` is completed, failed, cancelled, ... */
41
+ run?: V3RunState;
40
42
  error?: string;
41
43
  }
42
44
  /** Application-owned bridge to canonical V3 realtime. A subscription survives every turn.
package/dist/websocket.js CHANGED
@@ -165,7 +165,7 @@ export class SwarmWebSocketBridge {
165
165
  },
166
166
  onPermissionRemoved: permissionId => send({ type: 'permission_removed', permissionId }),
167
167
  onPermissionError: (error, permission) => send({ type: 'permission_error', permissionId: permission.id, error: error.message }),
168
- onComplete: messages => send({ type: 'done', messages }),
168
+ onComplete: (messages, run) => send({ type: 'done', messages, run }),
169
169
  onError: error => {
170
170
  send({ type: 'error', error: error.message });
171
171
  if (this.activeWatches.get(ws)?.watch === watch) {
@@ -4,6 +4,17 @@ export declare class SwarmWorkspacesNamespace {
4
4
  private transport;
5
5
  constructor(transport: SwarmTransport);
6
6
  createFolder(parent_path: string, name: string): Promise<WorkspaceFolder>;
7
+ /**
8
+ * Creates `parent_path/name` as a new, empty Git repository with one initial
9
+ * commit and registers it as a workspace, in one call. The daemon refuses a
10
+ * folder that already holds other content. Calling it again for a folder
11
+ * that is already registered returns that workspace.
12
+ */
13
+ create(body: {
14
+ parent_path: string;
15
+ name: string;
16
+ make_current?: boolean;
17
+ }): Promise<WorkspaceResolution>;
7
18
  add(body: {
8
19
  path: string;
9
20
  name?: string;
@@ -6,6 +6,23 @@ export class SwarmWorkspacesNamespace {
6
6
  async createFolder(parent_path, name) {
7
7
  return (await this.transport.request('/v1/workspace/folders/create', { method: 'POST', body: { parent_path, name } })).data.folder;
8
8
  }
9
+ /**
10
+ * Creates `parent_path/name` as a new, empty Git repository with one initial
11
+ * commit and registers it as a workspace, in one call. The daemon refuses a
12
+ * folder that already holds other content. Calling it again for a folder
13
+ * that is already registered returns that workspace.
14
+ */
15
+ async create(body) {
16
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$/.test(body.name)) {
17
+ throw new Error('Workspace name must be letters, digits, dashes and underscores (at most 63).');
18
+ }
19
+ const parent = body.parent_path.replace(/\/+$/, '');
20
+ if (!parent.startsWith('/'))
21
+ throw new Error('parent_path must be absolute.');
22
+ const path = `${parent}/${body.name}`;
23
+ await this.setupRepository(path, path);
24
+ return this.add({ path, name: body.name, make_current: body.make_current ?? false });
25
+ }
9
26
  async add(body) {
10
27
  return (await this.transport.request('/v1/workspace/add', { method: 'POST', body })).data.workspace;
11
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarm-agent/sdk",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "description": "Official TypeScript SDK for Swarm local AI coding workspace",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",