@clawos-dev/clawd 0.2.214 → 0.2.215

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/dist/cli.cjs CHANGED
@@ -48052,7 +48052,7 @@ function ticketMcpConfigPath(dataDir) {
48052
48052
  function writeTicketMcpConfig(args) {
48053
48053
  const cfgPath = ticketMcpConfigPath(args.dataDir);
48054
48054
  const content = {
48055
- _comment: "daemon \u542F\u52A8\u65F6\u81EA\u52A8\u751F\u6210\uFF1Bcc spawn \u901A\u8FC7 --mcp-config \u6CE8\u5165\u3002ticket-mcp-server.cjs \u662F daemon \u81EA\u5E26\u7684 stdio MCP server\uFF0CHTTP client \u8C03 clawos api /clawd-tickets/*\u3002env \u542B api url\u3001\u5199\u6B7B API key\u3001\u98DE\u4E66 unionId\u3001displayName snapshot\uFF1BCLAWD_TICKET_LOG \u6307 server \u5199\u65E5\u5FD7\u7684 append \u6587\u4EF6\u8DEF\u5F84\uFF08\u542F\u52A8\u8282\u70B9 + tool \u8C03\u7528 + \u9519\u8BEF\u7EC6\u8282\uFF09\u3002",
48055
+ _comment: "daemon \u542F\u52A8\u65F6\u81EA\u52A8\u751F\u6210\uFF1Bcc spawn \u901A\u8FC7 --mcp-config \u6CE8\u5165\u3002ticket-mcp-server.cjs \u662F daemon \u81EA\u5E26\u7684 stdio MCP server\uFF0CHTTP client \u8C03 clawos api /api/tickets/*\u3002env \u542B api url\u3001\u5199\u6B7B API key\u3001caller did/name/scene\uFF1BCLAWD_TICKET_LOG \u6307 server \u5199\u65E5\u5FD7\u7684 append \u6587\u4EF6\u8DEF\u5F84\u3002cc \u573A\u666F scene \u56FA\u5B9A persona\u3002",
48056
48056
  mcpServers: {
48057
48057
  "clawd-ticket": {
48058
48058
  type: "stdio",
@@ -48061,8 +48061,9 @@ function writeTicketMcpConfig(args) {
48061
48061
  env: {
48062
48062
  CLAWD_TICKET_API_URL: args.apiBaseUrl,
48063
48063
  CLAWD_TICKET_API_KEY: args.apiKey,
48064
- CLAWD_TICKET_OWNER_UNION_ID: args.ownerUnionId,
48065
- CLAWD_TICKET_OWNER_NAME: args.ownerName,
48064
+ CLAWD_TICKET_DID: args.did,
48065
+ CLAWD_TICKET_NAME: args.name,
48066
+ CLAWD_TICKET_SCENE: "persona",
48066
48067
  ...args.logPath ? { CLAWD_TICKET_LOG: args.logPath } : {}
48067
48068
  }
48068
48069
  }
@@ -59256,18 +59257,18 @@ async function startDaemon(config) {
59256
59257
  import_node_path64.default.join(here, "..", "dist", "ticket", "mcp-server.cjs")
59257
59258
  ];
59258
59259
  const ticketServerScriptPath = ticketServerCandidates.find((p2) => import_node_fs51.default.existsSync(p2));
59259
- const ticketOwnerUnionId = feishuIdentity?.identity.unionId ?? "";
59260
- const ticketOwnerName = feishuIdentity?.identity.displayName ?? "";
59260
+ const ticketCallerDid = feishuIdentity?.identity.unionId ?? "";
59261
+ const ticketCallerName = feishuIdentity?.identity.displayName ?? "";
59261
59262
  let ticketMcpConfigPath2;
59262
- if (ticketServerScriptPath && ticketOwnerUnionId) {
59263
+ if (ticketServerScriptPath && ticketCallerDid) {
59263
59264
  const ticketLogPath = import_node_path64.default.join(config.dataDir, "ticket-mcp-server.log");
59264
59265
  ticketMcpConfigPath2 = writeTicketMcpConfig({
59265
59266
  dataDir: config.dataDir,
59266
59267
  serverScriptPath: ticketServerScriptPath,
59267
59268
  apiBaseUrl: config.clawosApi ?? DEFAULT_CLAWOS_API,
59268
59269
  apiKey: resolveClawdTicketsApiKey(),
59269
- ownerUnionId: ticketOwnerUnionId,
59270
- ownerName: ticketOwnerName,
59270
+ did: ticketCallerDid,
59271
+ name: ticketCallerName,
59271
59272
  logPath: ticketLogPath
59272
59273
  });
59273
59274
  logger.info("ticket.mcp.json written", {
@@ -59277,7 +59278,7 @@ async function startDaemon(config) {
59277
59278
  });
59278
59279
  } else {
59279
59280
  logger.warn("ticket-mcp skipped", {
59280
- reason: !ticketOwnerUnionId ? "feishu unionId missing (login required)" : "ticket-mcp-server.cjs not found (need build)"
59281
+ reason: !ticketCallerDid ? "caller did missing (login required)" : "ticket-mcp-server.cjs not found (need build)"
59281
59282
  });
59282
59283
  }
59283
59284
  const shiftServerCandidates = [
@@ -6890,11 +6890,15 @@ var require_dist = __commonJS({
6890
6890
  // src/ticket/mcp-server.ts
6891
6891
  var mcp_server_exports = {};
6892
6892
  __export(mcp_server_exports, {
6893
+ handleAbandonTicket: () => handleAbandonTicket,
6893
6894
  handleAssignTicket: () => handleAssignTicket,
6894
6895
  handleClaimTicket: () => handleClaimTicket,
6895
6896
  handleCreateTicket: () => handleCreateTicket,
6896
6897
  handleGetTicket: () => handleGetTicket,
6897
6898
  handleListTickets: () => handleListTickets,
6899
+ handleOpenPr: () => handleOpenPr,
6900
+ handleReleaseTicket: () => handleReleaseTicket,
6901
+ handleStartTicket: () => handleStartTicket,
6898
6902
  handleUpdateTicket: () => handleUpdateTicket
6899
6903
  });
6900
6904
  module.exports = __toCommonJS(mcp_server_exports);
@@ -21112,9 +21116,49 @@ var StdioServerTransport = class {
21112
21116
  }
21113
21117
  };
21114
21118
 
21119
+ // src/ticket/api-client.ts
21120
+ function makeTicketApiClient(ctx) {
21121
+ const f = ctx.fetch ?? fetch;
21122
+ const base = ctx.apiUrl.replace(/\/+$/, "");
21123
+ async function call(method, pathAndQuery, body) {
21124
+ const url = `${base}/api${pathAndQuery}`;
21125
+ let res;
21126
+ try {
21127
+ res = await f(url, {
21128
+ method,
21129
+ headers: {
21130
+ Authorization: `Bearer ${ctx.apiKey}`,
21131
+ "X-Did": encodeURIComponent(ctx.did),
21132
+ "X-Name": encodeURIComponent(ctx.name),
21133
+ "X-Scene": ctx.scene,
21134
+ "content-type": "application/json"
21135
+ },
21136
+ body: body == null ? void 0 : JSON.stringify(body)
21137
+ });
21138
+ } catch (e) {
21139
+ return { ok: false, status: 0, message: `network error: ${String(e)}` };
21140
+ }
21141
+ const text = await res.text();
21142
+ if (!res.ok) {
21143
+ let message = `${res.status}`;
21144
+ try {
21145
+ const j = JSON.parse(text);
21146
+ if (typeof j.message === "string") message = j.message;
21147
+ else if (Array.isArray(j.message)) message = j.message.join("; ");
21148
+ else if (typeof j.errorCode === "string") message = j.errorCode;
21149
+ } catch {
21150
+ if (text) message = `${res.status}: ${text.slice(0, 200)}`;
21151
+ }
21152
+ return { ok: false, status: res.status, message };
21153
+ }
21154
+ return { ok: true, body: text };
21155
+ }
21156
+ return { call };
21157
+ }
21158
+
21115
21159
  // src/ticket/mcp-server.ts
21116
21160
  var SERVER_NAME = "clawd-ticket";
21117
- var SERVER_VERSION = "0.1.0";
21161
+ var SERVER_VERSION = "0.2.0";
21118
21162
  var TICKET_LOG = process.env.CLAWD_TICKET_LOG;
21119
21163
  function logLine(msg, info) {
21120
21164
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ticket-mcp ${msg}${info ? " " + JSON.stringify(info) : ""}
@@ -21137,157 +21181,181 @@ function ok(text) {
21137
21181
  function fail(text) {
21138
21182
  return { content: [{ type: "text", text }], isError: true };
21139
21183
  }
21140
- async function callApi(ctx, method, pathAndQuery, body) {
21141
- const f = ctx.fetch ?? fetch;
21142
- const base = ctx.apiUrl.replace(/\/+$/, "");
21143
- const url = `${base}/api${pathAndQuery}`;
21144
- logLine("tool call \u2192 api", { method, path: pathAndQuery });
21145
- let res;
21146
- try {
21147
- res = await f(url, {
21148
- method,
21149
- headers: {
21150
- Authorization: `Bearer ${ctx.apiKey}`,
21151
- "X-Owner-Union-Id": encodeURIComponent(ctx.ownerUnionId),
21152
- "X-Owner-Name": encodeURIComponent(ctx.ownerName),
21153
- "content-type": "application/json"
21154
- },
21155
- body: body == null ? void 0 : JSON.stringify(body)
21156
- });
21157
- } catch (e) {
21158
- logLine("fetch threw", { url, error: String(e) });
21159
- return fail(`network error calling ${url}: ${String(e)}`);
21160
- }
21161
- const text = await res.text();
21162
- if (!res.ok) {
21163
- logLine("non-2xx response", { url, status: res.status, body: text.slice(0, 200) });
21164
- let msg = `${res.status}`;
21165
- try {
21166
- const j = JSON.parse(text);
21167
- if (typeof j.message === "string") msg = `${res.status}: ${j.message}`;
21168
- else if (Array.isArray(j.message)) msg = `${res.status}: ${j.message.join("; ")}`;
21169
- else if (typeof j.errorCode === "string") msg = `${res.status}: ${j.errorCode}`;
21170
- } catch {
21171
- }
21172
- return fail(msg);
21173
- }
21174
- return ok(text);
21184
+ function unwrap(r) {
21185
+ if (r.ok) return ok(r.body);
21186
+ return fail(`${r.status}: ${r.message}`);
21175
21187
  }
21176
- async function handleCreateTicket(input, ctx) {
21177
- return callApi(ctx, "POST", "/clawd-tickets", {
21178
- title: input.title,
21179
- description: input.description
21180
- });
21188
+ async function handleCreateTicket(input, client) {
21189
+ logLine("tool: create_ticket", { title: input.title, type: input.type });
21190
+ return unwrap(await client.call("POST", "/tickets", input));
21181
21191
  }
21182
- async function handleListTickets(input, ctx) {
21192
+ async function handleListTickets(input, client) {
21183
21193
  const params = new URLSearchParams();
21184
- if (input.status && input.status.length > 0) params.set("status", input.status.join(","));
21194
+ if (input.status?.length) params.set("status", input.status.join(","));
21185
21195
  if (input.all) params.set("all", "true");
21196
+ if (input.version) params.set("version", input.version);
21186
21197
  const qs = params.toString() ? `?${params.toString()}` : "";
21187
- return callApi(ctx, "GET", `/clawd-tickets${qs}`);
21188
- }
21189
- async function handleGetTicket(input, ctx) {
21190
- return callApi(ctx, "GET", `/clawd-tickets/${encodeURIComponent(input.id)}`);
21191
- }
21192
- async function handleClaimTicket(input, ctx) {
21193
- return callApi(ctx, "POST", `/clawd-tickets/${encodeURIComponent(input.id)}/claim`, {});
21198
+ logLine("tool: list_tickets", { qs });
21199
+ return unwrap(await client.call("GET", `/tickets${qs}`));
21194
21200
  }
21195
- async function handleAssignTicket(input, ctx) {
21196
- return callApi(ctx, "POST", `/clawd-tickets/${encodeURIComponent(input.id)}/assign`, {
21197
- assignee_union_id: input.assignee_union_id,
21198
- assignee_name: input.assignee_name
21199
- });
21201
+ async function handleGetTicket(input, client) {
21202
+ return unwrap(await client.call("GET", `/tickets/${encodeURIComponent(input.id)}`));
21200
21203
  }
21201
- async function handleUpdateTicket(input, ctx) {
21204
+ async function handleUpdateTicket(input, client) {
21202
21205
  const patch = {};
21203
21206
  if (input.title !== void 0) patch.title = input.title;
21204
- if (input.description !== void 0) patch.description = input.description;
21205
- if (input.status !== void 0) patch.status = input.status;
21206
- return callApi(
21207
- ctx,
21208
- "PATCH",
21209
- `/clawd-tickets/${encodeURIComponent(input.id)}`,
21210
- patch
21207
+ if (input.summary !== void 0) patch.summary = input.summary;
21208
+ if (input.release_note !== void 0) patch.release_note = input.release_note;
21209
+ if (input.type !== void 0) patch.type = input.type;
21210
+ return unwrap(await client.call("PATCH", `/tickets/${encodeURIComponent(input.id)}`, patch));
21211
+ }
21212
+ async function handleClaimTicket(input, client) {
21213
+ return unwrap(await client.call("POST", `/tickets/${encodeURIComponent(input.id)}/claim`, {}));
21214
+ }
21215
+ async function handleReleaseTicket(input, client) {
21216
+ return unwrap(await client.call("POST", `/tickets/${encodeURIComponent(input.id)}/release`, {}));
21217
+ }
21218
+ async function handleAssignTicket(input, client) {
21219
+ return unwrap(
21220
+ await client.call("POST", `/tickets/${encodeURIComponent(input.id)}/assign`, {
21221
+ assignee_did: input.assignee_did,
21222
+ assignee_name: input.assignee_name
21223
+ })
21211
21224
  );
21212
21225
  }
21226
+ async function handleStartTicket(input, client) {
21227
+ return unwrap(await client.call("POST", `/tickets/${encodeURIComponent(input.id)}/start`, {}));
21228
+ }
21229
+ async function handleOpenPr(input, client) {
21230
+ return unwrap(
21231
+ await client.call("POST", `/tickets/${encodeURIComponent(input.id)}/open-pr`, {
21232
+ pr_url: input.pr_url
21233
+ })
21234
+ );
21235
+ }
21236
+ async function handleAbandonTicket(input, client) {
21237
+ return unwrap(await client.call("POST", `/tickets/${encodeURIComponent(input.id)}/abandon`, {}));
21238
+ }
21213
21239
  function readCtxFromEnv() {
21214
21240
  const apiUrl = process.env.CLAWD_TICKET_API_URL ?? "";
21215
21241
  const apiKey = process.env.CLAWD_TICKET_API_KEY ?? "";
21216
- const ownerUnionId = process.env.CLAWD_TICKET_OWNER_UNION_ID ?? "";
21217
- const ownerName = process.env.CLAWD_TICKET_OWNER_NAME ?? "";
21218
- if (!apiUrl || !apiKey || !ownerUnionId) {
21242
+ const did = process.env.CLAWD_TICKET_DID ?? "";
21243
+ const name = process.env.CLAWD_TICKET_NAME ?? "";
21244
+ const sceneRaw = process.env.CLAWD_TICKET_SCENE ?? "persona";
21245
+ const scene = sceneRaw === "daemon" || sceneRaw === "ci" || sceneRaw === "persona" ? sceneRaw : "persona";
21246
+ if (!apiUrl || !apiKey || !did) {
21219
21247
  logLine("startup: missing env", {
21220
21248
  hasApiUrl: !!apiUrl,
21221
21249
  hasApiKey: !!apiKey,
21222
- hasOwnerUnionId: !!ownerUnionId,
21223
- hasOwnerName: !!ownerName
21250
+ hasDid: !!did,
21251
+ hasName: !!name,
21252
+ scene
21224
21253
  });
21225
21254
  }
21226
- return { apiUrl, apiKey, ownerUnionId, ownerName };
21255
+ return { apiUrl, apiKey, did, name, scene };
21227
21256
  }
21228
21257
  async function main() {
21229
21258
  const ctx = readCtxFromEnv();
21230
21259
  logLine("startup: server boot", {
21231
- name: SERVER_NAME,
21260
+ server: SERVER_NAME,
21232
21261
  version: SERVER_VERSION,
21233
21262
  apiUrl: ctx.apiUrl,
21234
- ownerUnionId: ctx.ownerUnionId,
21235
- ownerName: ctx.ownerName,
21263
+ did: ctx.did,
21264
+ callerName: ctx.name,
21265
+ scene: ctx.scene,
21236
21266
  logTarget: TICKET_LOG ?? "stderr (no CLAWD_TICKET_LOG env)"
21237
21267
  });
21268
+ const client = makeTicketApiClient(ctx);
21238
21269
  const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
21270
+ const TICKET_STATUS_ENUM = [
21271
+ "open",
21272
+ "claimed",
21273
+ "in_progress",
21274
+ "pr_open",
21275
+ "shipped",
21276
+ "abandoned"
21277
+ ];
21278
+ const TICKET_TYPE_ENUM = ["feature", "improvement", "fix"];
21239
21279
  server.tool(
21240
21280
  "create_ticket",
21241
- 'Open a new ticket for owner-assigned work. status defaults to "open".',
21281
+ "Open a new ticket. summary is a developer-facing description; title is a one-liner. type picks the changelog bucket.",
21242
21282
  {
21243
21283
  title: external_exports.string().min(1),
21244
- description: external_exports.string().optional()
21284
+ summary: external_exports.string().min(1),
21285
+ type: external_exports.enum(TICKET_TYPE_ENUM)
21245
21286
  },
21246
- async (input) => handleCreateTicket(input, ctx)
21287
+ async (input) => handleCreateTicket(input, client)
21247
21288
  );
21248
21289
  server.tool(
21249
21290
  "list_tickets",
21250
- "List tickets. Default returns tickets where the caller is creator OR assignee (active statuses only). Pass status array to filter by status. Pass all: true to see every ticket regardless of who created or is assigned to it (admin / coordination view).",
21291
+ "List tickets. Default: caller is requester OR assignee, active statuses only (excludes shipped/abandoned). Pass status array to filter. Pass all=true to see every ticket. Pass version=<semver> to see tickets shipped in that release.",
21251
21292
  {
21252
- status: external_exports.array(external_exports.enum(["open", "in_progress", "done", "cancelled"])).optional(),
21253
- all: external_exports.boolean().optional()
21293
+ status: external_exports.array(external_exports.enum(TICKET_STATUS_ENUM)).optional(),
21294
+ all: external_exports.boolean().optional(),
21295
+ version: external_exports.string().optional()
21254
21296
  },
21255
- async (input) => handleListTickets(input, ctx)
21297
+ async (input) => handleListTickets(input, client)
21256
21298
  );
21257
21299
  server.tool(
21258
21300
  "get_ticket",
21259
- "Get one ticket by id (including full description).",
21260
- {
21261
- id: external_exports.string().min(1)
21262
- },
21263
- async (input) => handleGetTicket(input, ctx)
21301
+ "Get one ticket by id (T-<num>).",
21302
+ { id: external_exports.string().min(1) },
21303
+ async (input) => handleGetTicket(input, client)
21264
21304
  );
21265
21305
  server.tool(
21266
21306
  "update_ticket",
21267
- `Patch ticket fields (title / description / status). Caller must be the ticket's creator OR current assignee. To change the assignee, use claim_ticket or assign_ticket \u2014 assignee cannot be modified through update_ticket. Use status="done" when work is complete; "cancelled" when no longer needed. Append progress notes to description (read first, then append \u2014 do not overwrite).`,
21307
+ "Patch ticket fields (title / summary / release_note / type). Caller must be requester OR current assignee. Assignee / status / version / pr_url / commit_sha cannot be modified here \u2014 use claim/release/assign/start/open_pr/abandon endpoints.",
21268
21308
  {
21269
21309
  id: external_exports.string().min(1),
21270
21310
  title: external_exports.string().optional(),
21271
- description: external_exports.string().nullable().optional(),
21272
- status: external_exports.enum(["open", "in_progress", "done", "cancelled"]).optional()
21311
+ summary: external_exports.string().optional(),
21312
+ release_note: external_exports.string().nullable().optional(),
21313
+ type: external_exports.enum(TICKET_TYPE_ENUM).optional()
21273
21314
  },
21274
- async (input) => handleUpdateTicket(input, ctx)
21315
+ async (input) => handleUpdateTicket(input, client)
21275
21316
  );
21276
21317
  server.tool(
21277
21318
  "claim_ticket",
21278
- "Claim an unassigned ticket \u2014 atomically set the assignee to the current caller. Precondition: the ticket's assignee must be NULL (open for claim). Returns 409 ASSIGNEE_LOCKED if already taken, 404 if the ticket does not exist. Anyone can claim a ticket they did not create.",
21319
+ "Claim an unassigned ticket (status open \u2192 claimed). Fails 409 ASSIGNEE_LOCKED if already taken.",
21279
21320
  { id: external_exports.string().min(1) },
21280
- async (input) => handleClaimTicket(input, ctx)
21321
+ async (input) => handleClaimTicket(input, client)
21322
+ );
21323
+ server.tool(
21324
+ "release_ticket",
21325
+ "Release a ticket back to unassigned (status \u2192 open). Only current assignee or requester can release. Precondition to reassigning (\u5148\u91CA\u653E\u518D\u8BA4\u9886).",
21326
+ { id: external_exports.string().min(1) },
21327
+ async (input) => handleReleaseTicket(input, client)
21281
21328
  );
21282
21329
  server.tool(
21283
21330
  "assign_ticket",
21284
- "Assign an unassigned ticket to a specific person \u2014 only the ticket's creator can do this. Precondition: the ticket's assignee must be NULL. Returns 403 NOT_CREATOR if caller is not the creator, 409 ASSIGNEE_LOCKED if already taken, 404 if not found. The assignee_union_id and assignee_name must be supplied by the caller (no user-directory lookup is available).",
21331
+ "Requester assigns an unassigned ticket to someone. Precondition: assignee is null. Fails 403 if caller is not the requester.",
21285
21332
  {
21286
21333
  id: external_exports.string().min(1),
21287
- assignee_union_id: external_exports.string().min(1),
21334
+ assignee_did: external_exports.string().min(1),
21288
21335
  assignee_name: external_exports.string().min(1)
21289
21336
  },
21290
- async (input) => handleAssignTicket(input, ctx)
21337
+ async (input) => handleAssignTicket(input, client)
21338
+ );
21339
+ server.tool(
21340
+ "start_ticket",
21341
+ "Move a ticket claimed \u2192 in_progress. Only the current assignee can start.",
21342
+ { id: external_exports.string().min(1) },
21343
+ async (input) => handleStartTicket(input, client)
21344
+ );
21345
+ server.tool(
21346
+ "open_pr",
21347
+ 'Move a ticket claimed/in_progress \u2192 pr_open with a PR URL. Only the current assignee can open-pr. PR body must include "Ticket: T-<num>" trailer for CI to auto-flip shipped on merge.',
21348
+ {
21349
+ id: external_exports.string().min(1),
21350
+ pr_url: external_exports.string().url()
21351
+ },
21352
+ async (input) => handleOpenPr(input, client)
21353
+ );
21354
+ server.tool(
21355
+ "abandon_ticket",
21356
+ "Requester abandons a ticket (any non-shipped status \u2192 abandoned). Terminal state.",
21357
+ { id: external_exports.string().min(1) },
21358
+ async (input) => handleAbandonTicket(input, client)
21291
21359
  );
21292
21360
  const transport = new StdioServerTransport();
21293
21361
  await server.connect(transport);
@@ -21301,10 +21369,14 @@ if (require.main === module) {
21301
21369
  }
21302
21370
  // Annotate the CommonJS export names for ESM import in node:
21303
21371
  0 && (module.exports = {
21372
+ handleAbandonTicket,
21304
21373
  handleAssignTicket,
21305
21374
  handleClaimTicket,
21306
21375
  handleCreateTicket,
21307
21376
  handleGetTicket,
21308
21377
  handleListTickets,
21378
+ handleOpenPr,
21379
+ handleReleaseTicket,
21380
+ handleStartTicket,
21309
21381
  handleUpdateTicket
21310
21382
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.214",
3
+ "version": "0.2.215",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",