@islamihab/kds 0.2.1 → 0.2.3

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 (2) hide show
  1. package/index.js +265 -96
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -14383,6 +14383,9 @@ config(en_default());
14383
14383
  // ../../packages/env/src/cli.ts
14384
14384
  var env = createEnv({
14385
14385
  server: {
14386
+ KDS_ACTOR: exports_external.enum(["agent", "human"]).optional(),
14387
+ KDS_HARNESS: exports_external.string().optional(),
14388
+ KDS_MODEL: exports_external.string().optional(),
14386
14389
  KDS_CONFIG_DIR: exports_external.string().optional(),
14387
14390
  KDS_NO_UPDATE_CHECK: exports_external.string().optional(),
14388
14391
  KDS_SERVER_URL: exports_external.url().optional()
@@ -14594,7 +14597,7 @@ import { join } from "path";
14594
14597
  // package.json
14595
14598
  var package_default = {
14596
14599
  name: "cli",
14597
- version: "0.2.1",
14600
+ version: "0.2.3",
14598
14601
  private: true,
14599
14602
  type: "module",
14600
14603
  bin: {
@@ -17623,7 +17626,62 @@ function forwardErrorData(errorData, error51) {
17623
17626
  error51.data = jsonToConvex(errorData);
17624
17627
  return error51;
17625
17628
  }
17629
+ // src/lib/actor.ts
17630
+ import { homedir as homedir2 } from "os";
17631
+ import { join as join2 } from "path";
17632
+ var HARNESSES = [
17633
+ { env: "CLAUDECODE", label: "Claude Code" },
17634
+ { env: "CLAUDE_CODE_ENTRYPOINT", label: "Claude Code" },
17635
+ { env: "CODEX_SANDBOX", label: "Codex" },
17636
+ { env: "CURSOR_AGENT", label: "Cursor" },
17637
+ { env: "AI_AGENT", label: undefined }
17638
+ ];
17639
+ var readEnv = (name) => {
17640
+ const value = process.env[name]?.trim();
17641
+ return value ? value : undefined;
17642
+ };
17643
+ var detectHarness = () => {
17644
+ if (env.KDS_HARNESS)
17645
+ return env.KDS_HARNESS;
17646
+ for (const { env: name, label } of HARNESSES) {
17647
+ const value = readEnv(name);
17648
+ if (value)
17649
+ return label ?? value;
17650
+ }
17651
+ return;
17652
+ };
17653
+ var TRANSCRIPT_TAIL_BYTES = 64 * 1024;
17654
+ var claudeCodeModel = async () => {
17655
+ const sessionId = readEnv("CLAUDE_CODE_SESSION_ID");
17656
+ if (!sessionId)
17657
+ return;
17658
+ try {
17659
+ const projects = join2(homedir2(), ".claude", "projects");
17660
+ const glob = new Bun.Glob(`*/${sessionId}.jsonl`);
17661
+ for await (const match of glob.scan({ cwd: projects, absolute: true })) {
17662
+ const file2 = Bun.file(match);
17663
+ const tail = await file2.slice(Math.max(0, file2.size - TRANSCRIPT_TAIL_BYTES)).text();
17664
+ const models = [...tail.matchAll(/"model"\s*:\s*"([^"]+)"/g)];
17665
+ return models.at(-1)?.[1];
17666
+ }
17667
+ } catch {}
17668
+ return;
17669
+ };
17670
+ var detectModel = async () => env.KDS_MODEL ?? readEnv("ANTHROPIC_MODEL") ?? await claudeCodeModel();
17671
+ var detectAgent = async () => {
17672
+ if (env.KDS_ACTOR === "human")
17673
+ return;
17674
+ const harness = detectHarness();
17675
+ if (!harness && env.KDS_ACTOR !== "agent")
17676
+ return;
17677
+ const model = await detectModel();
17678
+ return { harness: harness ?? "unidentified harness", ...model ? { model } : {} };
17679
+ };
17680
+ var claim;
17681
+ var agentClaim = () => claim ??= detectAgent();
17682
+
17626
17683
  // src/lib/backend.ts
17684
+ var attributed = (args, agent) => agent === undefined ? args : { ...args, agent };
17627
17685
  var backendClient = async () => {
17628
17686
  const config2 = await loadConfig();
17629
17687
  if (!config2)
@@ -17641,7 +17699,17 @@ var backendClient = async () => {
17641
17699
  throw new Error("The auth server returned no Convex token");
17642
17700
  client3.setAuth(data.token);
17643
17701
  }
17644
- return client3;
17702
+ return {
17703
+ query: (reference, ...args) => client3.query(reference, ...args),
17704
+ mutation: async (reference, args) => {
17705
+ const widened = reference;
17706
+ return await client3.mutation(widened, attributed(args, await agentClaim()));
17707
+ },
17708
+ action: async (reference, args) => {
17709
+ const widened = reference;
17710
+ return await client3.action(widened, attributed(args, await agentClaim()));
17711
+ }
17712
+ };
17645
17713
  };
17646
17714
 
17647
17715
  // src/lib/prompt.ts
@@ -17804,6 +17872,72 @@ var logout = command({
17804
17872
  }
17805
17873
  });
17806
17874
 
17875
+ // src/lib/activity.ts
17876
+ var SOURCE_LABELS = { github: "GitHub", agent: "Agent" };
17877
+ var actorLabel = (via, agent) => {
17878
+ if (via === undefined)
17879
+ return "You";
17880
+ const named = [agent?.model, agent?.harness].filter(Boolean).join(" \xB7 ");
17881
+ return named ? `${SOURCE_LABELS[via]} (${named})` : SOURCE_LABELS[via];
17882
+ };
17883
+ var issueActivityLine = (detail) => {
17884
+ const label = detail.kind.replaceAll("_", " ");
17885
+ switch (detail.kind) {
17886
+ case "created":
17887
+ case "description_changed":
17888
+ return label;
17889
+ case "title_changed":
17890
+ case "status_changed":
17891
+ case "priority_changed":
17892
+ case "disposition_changed":
17893
+ case "estimate_changed":
17894
+ case "due_date_changed":
17895
+ case "branch_changed":
17896
+ case "pr_changed":
17897
+ case "project_changed":
17898
+ case "milestone_changed":
17899
+ case "parent_changed":
17900
+ return `${label}: ${detail.from ?? "none"} \u2192 ${detail.to ?? "none"}`;
17901
+ case "label_added":
17902
+ case "label_removed":
17903
+ case "attachment_added":
17904
+ case "attachment_removed":
17905
+ return `${label}: ${detail.name}`;
17906
+ case "child_added":
17907
+ case "child_removed":
17908
+ return `${label}: ${detail.identifier}`;
17909
+ case "relation_added":
17910
+ case "relation_removed":
17911
+ return `${label}: ${detail.relation.replaceAll("_", " ")} ${detail.identifier}`;
17912
+ }
17913
+ };
17914
+ var projectActivityLine = (detail) => {
17915
+ const label = detail.kind.replaceAll("_", " ");
17916
+ switch (detail.kind) {
17917
+ case "created":
17918
+ case "summary_changed":
17919
+ case "description_changed":
17920
+ case "milestones_reordered":
17921
+ return label;
17922
+ case "name_changed":
17923
+ case "status_changed":
17924
+ case "health_changed":
17925
+ case "target_date_changed":
17926
+ case "repo_changed":
17927
+ case "milestone_renamed":
17928
+ return `${label}: ${detail.from ?? "none"} \u2192 ${detail.to ?? "none"}`;
17929
+ case "milestone_added":
17930
+ case "milestone_removed":
17931
+ case "milestone_description_changed":
17932
+ return `${label}: ${detail.name}`;
17933
+ case "milestone_target_date_changed":
17934
+ return `${label}: ${detail.name} ${detail.from ?? "none"} \u2192 ${detail.to ?? "none"}`;
17935
+ case "issue_added":
17936
+ case "issue_removed":
17937
+ return `${label}: ${detail.identifier}`;
17938
+ }
17939
+ };
17940
+
17807
17941
  // src/commands/auth/status.ts
17808
17942
  var status = command({
17809
17943
  name: "status",
@@ -17813,6 +17947,9 @@ var status = command({
17813
17947
  if (!user)
17814
17948
  return console.log("Not signed in.");
17815
17949
  console.log(`Signed in as ${user.name} <${user.email}>`);
17950
+ const agent = await agentClaim();
17951
+ if (agent)
17952
+ console.log(`Acting as ${actorLabel("agent", agent)}; changes will show in the inbox.`);
17816
17953
  }
17817
17954
  });
17818
17955
 
@@ -17823,57 +17960,55 @@ var auth = group({
17823
17960
  commands: [login, logout, status]
17824
17961
  });
17825
17962
 
17826
- // src/lib/attachments.ts
17827
- import { basename } from "path";
17828
- var attachFiles = async (client3, issueId, paths) => {
17829
- if (paths.length === 0)
17830
- return [];
17831
- const files = paths.map((path) => ({ path, file: Bun.file(path) }));
17832
- for (const { path, file: file2 } of files) {
17833
- if (!await file2.exists())
17834
- throw new Error(`No file at ${path}.`);
17835
- if (file2.size <= 0)
17836
- throw new Error(`${path} is empty; an attachment cannot be.`);
17837
- if (file2.size > MAX_ISSUE_ATTACHMENT_BYTES) {
17838
- throw new Error(`${path} is too large (${file2.size} bytes; max ${MAX_ISSUE_ATTACHMENT_BYTES}).`);
17839
- }
17840
- }
17841
- const created = new Set;
17842
- try {
17843
- for (const { path, file: file2 } of files) {
17844
- const uploadUrl = await client3.mutation(api2.issueAttachments.generateUploadUrl, { issueId });
17845
- const response = await fetch(uploadUrl, {
17846
- method: "POST",
17847
- headers: { "Content-Type": file2.type },
17848
- body: file2
17849
- });
17850
- if (!response.ok)
17851
- throw new Error(`Uploading ${path} failed (${response.status}).`);
17852
- const storageId = exports_external.custom((value) => typeof value === "string");
17853
- const body = exports_external.object({ storageId }).safeParse(await response.json());
17854
- if (!body.success)
17855
- throw new Error("The upload returned no file reference.");
17856
- created.add(await client3.action(api2.issueAttachments.create, {
17857
- issueId,
17858
- storageId: body.data.storageId,
17859
- name: basename(path)
17860
- }));
17861
- }
17862
- } catch (error51) {
17863
- for (const id of created) {
17864
- await client3.mutation(api2.issueAttachments.remove, { id }).catch(() => {
17865
- console.error("An attachment from this failed batch could not be removed; check the dashboard.");
17866
- });
17867
- }
17868
- throw error51;
17963
+ // src/lib/output.ts
17964
+ var printTable = (rows) => {
17965
+ if (rows.length === 0)
17966
+ return;
17967
+ const columnCount = Math.max(...rows.map((row) => row.length));
17968
+ const widths = Array.from({ length: columnCount }, (_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
17969
+ for (const row of rows) {
17970
+ console.log(row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd());
17869
17971
  }
17870
- const attachments = await client3.query(api2.issueAttachments.list, { issueId });
17871
- return attachments.filter((attachment) => created.has(attachment._id));
17872
17972
  };
17873
- var printAttachments = (attachments) => {
17874
- for (const attachment of attachments) {
17875
- console.log(`Attached ${attachment.name}${attachment.url ? ` \u2014 ${attachment.url}` : ""}`);
17973
+
17974
+ // src/commands/inbox/list.ts
17975
+ var inboxOptions = {
17976
+ all: exports_external.boolean().default(false).describe("Include events already marked seen"),
17977
+ json: exports_external.boolean().default(false).describe("Print as JSON")
17978
+ };
17979
+ var runInbox = async (options) => {
17980
+ const client3 = await backendClient();
17981
+ const inbox = await client3.query(api2.inbox.list, {});
17982
+ const targetLastSeen = new Map(inbox.targetLastSeen.map((state) => [state.targetId, state.lastSeenAt]));
17983
+ const events = options.all ? inbox.events : inbox.events.filter((event) => {
17984
+ const targetId = event.type === "issue" ? event.issue.id : event.project.id;
17985
+ return event.at > Math.max(inbox.lastSeenAt, targetLastSeen.get(targetId) ?? 0);
17986
+ });
17987
+ if (options.json)
17988
+ return console.log(JSON.stringify({ ...inbox, events }, null, 2));
17989
+ if (events.length === 0) {
17990
+ if (options.all)
17991
+ console.log("Nothing in the inbox.");
17992
+ else
17993
+ console.log(inbox.truncated ? "Nothing unseen in the newest events shown." : "Inbox zero.");
17994
+ if (!options.all && inbox.events.length > 0)
17995
+ console.log("Pass --all to reprint seen events.");
17996
+ return;
17876
17997
  }
17998
+ printTable([
17999
+ ["WHEN", "ACTOR", "WHERE", "EVENT"],
18000
+ ...events.map((event) => [
18001
+ new Date(event.at).toISOString().slice(0, 16).replace("T", " "),
18002
+ actorLabel(event.via, event.agent),
18003
+ event.type === "issue" ? event.issue.identifier : event.project.name,
18004
+ event.type === "issue" ? issueActivityLine(event.detail) : projectActivityLine(event.detail)
18005
+ ])
18006
+ ]);
18007
+ if (inbox.truncated)
18008
+ console.log(`
18009
+ (older events truncated)`);
18010
+ if (!options.all)
18011
+ console.log("\nRun `kds inbox seen` once these are handled.");
17877
18012
  };
17878
18013
 
17879
18014
  // src/lib/input.ts
@@ -18011,6 +18146,83 @@ var resolveMilestone = async (client3, projectId, name) => {
18011
18146
  return milestone;
18012
18147
  };
18013
18148
 
18149
+ // src/commands/inbox/seen.ts
18150
+ var seen = command({
18151
+ name: "seen",
18152
+ description: "Mark one issue, or the whole inbox, as seen",
18153
+ positionals: {
18154
+ id: exports_external.string().optional().describe("Issue identifier, number, or URL")
18155
+ },
18156
+ run: async ({ positionals: { id } }) => {
18157
+ const client3 = await backendClient();
18158
+ const issue2 = id === undefined ? undefined : await resolveIssue(client3, id);
18159
+ await client3.mutation(api2.inbox.markSeen, { targetId: issue2?._id });
18160
+ console.log(issue2 ? `${issue2.identifier} marked seen.` : "Inbox marked seen.");
18161
+ }
18162
+ });
18163
+
18164
+ // src/commands/inbox/index.ts
18165
+ var inbox = group({
18166
+ name: "inbox",
18167
+ description: "Automated activity across issues and projects, unseen first",
18168
+ options: inboxOptions,
18169
+ run: async ({ options }) => await runInbox(options),
18170
+ commands: [seen]
18171
+ });
18172
+
18173
+ // src/lib/attachments.ts
18174
+ import { basename } from "path";
18175
+ var attachFiles = async (client3, issueId, paths) => {
18176
+ if (paths.length === 0)
18177
+ return [];
18178
+ const files = paths.map((path) => ({ path, file: Bun.file(path) }));
18179
+ for (const { path, file: file2 } of files) {
18180
+ if (!await file2.exists())
18181
+ throw new Error(`No file at ${path}.`);
18182
+ if (file2.size <= 0)
18183
+ throw new Error(`${path} is empty; an attachment cannot be.`);
18184
+ if (file2.size > MAX_ISSUE_ATTACHMENT_BYTES) {
18185
+ throw new Error(`${path} is too large (${file2.size} bytes; max ${MAX_ISSUE_ATTACHMENT_BYTES}).`);
18186
+ }
18187
+ }
18188
+ const created = new Set;
18189
+ try {
18190
+ for (const { path, file: file2 } of files) {
18191
+ const uploadUrl = await client3.mutation(api2.issueAttachments.generateUploadUrl, { issueId });
18192
+ const response = await fetch(uploadUrl, {
18193
+ method: "POST",
18194
+ headers: { "Content-Type": file2.type },
18195
+ body: file2
18196
+ });
18197
+ if (!response.ok)
18198
+ throw new Error(`Uploading ${path} failed (${response.status}).`);
18199
+ const storageId = exports_external.custom((value) => typeof value === "string");
18200
+ const body = exports_external.object({ storageId }).safeParse(await response.json());
18201
+ if (!body.success)
18202
+ throw new Error("The upload returned no file reference.");
18203
+ created.add(await client3.action(api2.issueAttachments.create, {
18204
+ issueId,
18205
+ storageId: body.data.storageId,
18206
+ name: basename(path)
18207
+ }));
18208
+ }
18209
+ } catch (error51) {
18210
+ for (const id of created) {
18211
+ await client3.mutation(api2.issueAttachments.remove, { id }).catch(() => {
18212
+ console.error("An attachment from this failed batch could not be removed; check the dashboard.");
18213
+ });
18214
+ }
18215
+ throw error51;
18216
+ }
18217
+ const attachments = await client3.query(api2.issueAttachments.list, { issueId });
18218
+ return attachments.filter((attachment) => created.has(attachment._id));
18219
+ };
18220
+ var printAttachments = (attachments) => {
18221
+ for (const attachment of attachments) {
18222
+ console.log(`Attached ${attachment.name}${attachment.url ? ` \u2014 ${attachment.url}` : ""}`);
18223
+ }
18224
+ };
18225
+
18014
18226
  // src/commands/issues/comment.ts
18015
18227
  var comment = command({
18016
18228
  name: "comment",
@@ -18073,38 +18285,6 @@ var create = command({
18073
18285
  });
18074
18286
 
18075
18287
  // src/commands/issues/get.ts
18076
- var SOURCE_LABELS = { github: "GitHub" };
18077
- var activityLine = (detail) => {
18078
- const label = detail.kind.replaceAll("_", " ");
18079
- switch (detail.kind) {
18080
- case "created":
18081
- case "description_changed":
18082
- return label;
18083
- case "title_changed":
18084
- case "status_changed":
18085
- case "priority_changed":
18086
- case "disposition_changed":
18087
- case "estimate_changed":
18088
- case "due_date_changed":
18089
- case "branch_changed":
18090
- case "pr_changed":
18091
- case "project_changed":
18092
- case "milestone_changed":
18093
- case "parent_changed":
18094
- return `${label}: ${detail.from ?? "none"} \u2192 ${detail.to ?? "none"}`;
18095
- case "label_added":
18096
- case "label_removed":
18097
- case "attachment_added":
18098
- case "attachment_removed":
18099
- return `${label}: ${detail.name}`;
18100
- case "child_added":
18101
- case "child_removed":
18102
- return `${label}: ${detail.identifier}`;
18103
- case "relation_added":
18104
- case "relation_removed":
18105
- return `${label}: ${detail.relation.replaceAll("_", " ")} ${detail.identifier}`;
18106
- }
18107
- };
18108
18288
  var get = command({
18109
18289
  name: "get",
18110
18290
  description: "Show an issue: properties, description, and its feed",
@@ -18172,8 +18352,8 @@ Feed:`);
18172
18352
  for (const event of feed.events) {
18173
18353
  const at = new Date(event.at).toISOString().slice(0, 16).replace("T", " ");
18174
18354
  if (event.type === "activity") {
18175
- const via = event.via === undefined ? "" : ` (via ${SOURCE_LABELS[event.via]})`;
18176
- console.log(`${at} ${activityLine(event.detail)}${via}`);
18355
+ const via = event.via === undefined ? "" : ` \u2014 ${actorLabel(event.via, event.agent)}`;
18356
+ console.log(`${at} ${issueActivityLine(event.detail)}${via}`);
18177
18357
  } else {
18178
18358
  console.log(`${at} comment${event.editedAt ? " (edited)" : ""}:`);
18179
18359
  for (const line of event.bodyMarkdown.split(`
@@ -18184,17 +18364,6 @@ Feed:`);
18184
18364
  }
18185
18365
  });
18186
18366
 
18187
- // src/lib/output.ts
18188
- var printTable = (rows) => {
18189
- if (rows.length === 0)
18190
- return;
18191
- const columnCount = Math.max(...rows.map((row) => row.length));
18192
- const widths = Array.from({ length: columnCount }, (_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
18193
- for (const row of rows) {
18194
- console.log(row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd());
18195
- }
18196
- };
18197
-
18198
18367
  // src/commands/issues/list.ts
18199
18368
  var list = command({
18200
18369
  name: "list",
@@ -18888,7 +19057,7 @@ var upgrade = command({
18888
19057
  var rootCommand = group({
18889
19058
  name: "kds",
18890
19059
  description: "KDS CLI",
18891
- commands: [auth, issues, pages, project, projects, upgrade],
19060
+ commands: [auth, inbox, issues, pages, project, projects, upgrade],
18892
19061
  options: {
18893
19062
  version: exports_external.boolean().default(false).describe("Show the version").meta({ short: "v" })
18894
19063
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@islamihab/kds",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Command-line client for Kai Dev Studio",
5
5
  "license": "MIT",
6
6
  "publishConfig": {