@alook/cli 0.0.13 → 0.0.15

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/index.js CHANGED
@@ -14118,6 +14118,9 @@ var TestEmailConnectionSchema = exports_external.object({
14118
14118
  smtpPassword: exports_external.string().min(1),
14119
14119
  smtpTls: exports_external.number().int().min(0).max(2).default(1)
14120
14120
  });
14121
+ var UpdateMemberRequestSchema = exports_external.object({
14122
+ global_instruction: exports_external.string().max(50000).trim()
14123
+ });
14121
14124
  var CreateWorkspaceRequestSchema = exports_external.object({
14122
14125
  name: exports_external.string().min(1, "name is required"),
14123
14126
  slug: exports_external.string().min(1, "slug is required")
@@ -15550,6 +15553,7 @@ var member = sqliteTable("member", {
15550
15553
  workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15551
15554
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
15552
15555
  role: text("role").notNull().default("member"),
15556
+ globalInstruction: text("global_instruction").notNull().default(""),
15553
15557
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15554
15558
  }, (t) => [unique("member_workspace_user").on(t.workspaceId, t.userId)]);
15555
15559
  var machine = sqliteTable("machine", {
@@ -15655,6 +15659,7 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
15655
15659
  error: text("error")
15656
15660
  }, (t) => [
15657
15661
  index("idx_task_queue_pending").on(t.agentId, t.status).where(sql`status IN ('queued', 'dispatched')`),
15662
+ index("idx_task_queue_workspace_active").on(t.workspaceId, t.status, t.agentId).where(sql`status IN ('queued', 'dispatched', 'running')`),
15658
15663
  foreignKey({
15659
15664
  columns: [t.agentId, t.workspaceId],
15660
15665
  foreignColumns: [agent.id, agent.workspaceId]
@@ -15821,16 +15826,33 @@ class DaemonClient {
15821
15826
  "Content-Type": "application/json",
15822
15827
  Authorization: `Bearer ${token}`
15823
15828
  };
15824
- const res = await fetch(this.baseURL + path, {
15825
- method,
15826
- headers,
15827
- body: body ? JSON.stringify(body) : undefined
15828
- });
15829
- if (!res.ok)
15830
- throw new Error(`HTTP ${res.status}: ${await res.text()}`);
15831
- if (res.status === 204)
15832
- return;
15833
- return res.json();
15829
+ const MAX_RETRIES = 3;
15830
+ const BASE_DELAY_MS = 500;
15831
+ let lastError;
15832
+ for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
15833
+ try {
15834
+ const res = await fetch(this.baseURL + path, {
15835
+ method,
15836
+ headers,
15837
+ body: body ? JSON.stringify(body) : undefined
15838
+ });
15839
+ if (!res.ok)
15840
+ throw new Error(`HTTP ${res.status}: ${await res.text()}`);
15841
+ if (res.status === 204)
15842
+ return;
15843
+ return res.json();
15844
+ } catch (e) {
15845
+ if (e instanceof TypeError) {
15846
+ lastError = e;
15847
+ if (attempt < MAX_RETRIES) {
15848
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS * 2 ** attempt));
15849
+ continue;
15850
+ }
15851
+ }
15852
+ throw e;
15853
+ }
15854
+ }
15855
+ throw lastError;
15834
15856
  }
15835
15857
  async register(token, body) {
15836
15858
  const raw = await this.request("POST", "/api/daemon/register", token, body);
@@ -15865,11 +15887,28 @@ class DaemonClient {
15865
15887
  return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
15866
15888
  }
15867
15889
  async downloadArtifact(token, artifactId, workspaceId) {
15868
- const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
15869
- if (!res.ok) {
15870
- throw new Error(`artifact download failed: HTTP ${res.status}`);
15890
+ const MAX_RETRIES = 3;
15891
+ const BASE_DELAY_MS = 500;
15892
+ let lastError;
15893
+ for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
15894
+ try {
15895
+ const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
15896
+ if (!res.ok) {
15897
+ throw new Error(`artifact download failed: HTTP ${res.status}`);
15898
+ }
15899
+ return res.arrayBuffer();
15900
+ } catch (e) {
15901
+ if (e instanceof TypeError) {
15902
+ lastError = e;
15903
+ if (attempt < MAX_RETRIES) {
15904
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS * 2 ** attempt));
15905
+ continue;
15906
+ }
15907
+ }
15908
+ throw e;
15909
+ }
15871
15910
  }
15872
- return res.arrayBuffer();
15911
+ throw lastError;
15873
15912
  }
15874
15913
  reportMessages(token, taskId, messages) {
15875
15914
  return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
@@ -16751,7 +16790,7 @@ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, readFileSync
16751
16790
  import { basename, join as join5 } from "path";
16752
16791
  import PostalMime from "postal-mime";
16753
16792
  var VALID_STATUSES = ["unread", "read", "archived"];
16754
- var EMAIL_DIR = "/tmp/alook-emails";
16793
+ var EMAIL_BASE = "/tmp/alook-emails";
16755
16794
  var MIME_BY_EXT = {
16756
16795
  ".pdf": "application/pdf",
16757
16796
  ".png": "image/png",
@@ -16801,13 +16840,14 @@ function resolveClientOpts(command, opts) {
16801
16840
  }
16802
16841
  function emailCommand() {
16803
16842
  const cmd = new Command5("email").description("Manage agent emails");
16804
- cmd.command("pull").description("Download and parse emails to /tmp/alook-emails/").requiredOption("--agent_id <id>", "Agent ID").option("--status <status>", "Filter by status (unread, read, archived)").option("--workspace <id>", "Workspace ID").option("--json", "Output as JSON instead of files").action(async (opts, command) => {
16843
+ cmd.command("pull").description("Download and parse emails to /tmp/alook-emails/{workspaceId}/{agentId}/").requiredOption("--agent_id <id>", "Agent ID").option("--status <status>", "Filter by status (unread, read, archived)").option("--workspace <id>", "Workspace ID").option("--json", "Output as JSON instead of files").action(async (opts, command) => {
16805
16844
  const { serverUrl, token, workspaceId } = resolveClientOpts(command, { workspace: opts.workspace, agentId: opts.agent_id });
16806
16845
  const client = new APIClient(serverUrl, token, workspaceId);
16807
16846
  if (opts.status && !VALID_STATUSES.includes(opts.status)) {
16808
16847
  console.error(`Error: invalid status "${opts.status}", must be one of: ${VALID_STATUSES.join(", ")}`);
16809
16848
  process.exit(1);
16810
16849
  }
16850
+ const emailDir_base = join5(EMAIL_BASE, workspaceId, opts.agent_id);
16811
16851
  try {
16812
16852
  let query = `/api/email?agentId=${opts.agent_id}`;
16813
16853
  if (opts.status)
@@ -16821,10 +16861,10 @@ function emailCommand() {
16821
16861
  printJSON(emails2);
16822
16862
  return;
16823
16863
  }
16824
- mkdirSync5(EMAIL_DIR, { recursive: true });
16864
+ mkdirSync5(emailDir_base, { recursive: true });
16825
16865
  const downloadedPaths = [];
16826
16866
  for (const email3 of emails2) {
16827
- const emailDir = join5(EMAIL_DIR, email3.id);
16867
+ const emailDir = join5(emailDir_base, email3.id);
16828
16868
  mkdirSync5(emailDir, { recursive: true });
16829
16869
  const metadata = {
16830
16870
  id: email3.id,
@@ -16888,7 +16928,7 @@ function emailCommand() {
16888
16928
  }
16889
16929
  }
16890
16930
  }
16891
- console.log(`Downloaded ${emails2.length} email${emails2.length === 1 ? "" : "s"} to ${EMAIL_DIR}/`);
16931
+ console.log(`Downloaded ${emails2.length} email${emails2.length === 1 ? "" : "s"} to ${emailDir_base}/`);
16892
16932
  for (const p of downloadedPaths) {
16893
16933
  console.log(` ${p}`);
16894
16934
  }
@@ -13835,6 +13835,9 @@ var TestEmailConnectionSchema = exports_external.object({
13835
13835
  smtpPassword: exports_external.string().min(1),
13836
13836
  smtpTls: exports_external.number().int().min(0).max(2).default(1)
13837
13837
  });
13838
+ var UpdateMemberRequestSchema = exports_external.object({
13839
+ global_instruction: exports_external.string().max(50000).trim()
13840
+ });
13838
13841
  var CreateWorkspaceRequestSchema = exports_external.object({
13839
13842
  name: exports_external.string().min(1, "name is required"),
13840
13843
  slug: exports_external.string().min(1, "slug is required")
@@ -15267,6 +15270,7 @@ var member = sqliteTable("member", {
15267
15270
  workspaceId: text("workspace_id").notNull().references(() => workspace.id, { onDelete: "cascade" }),
15268
15271
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
15269
15272
  role: text("role").notNull().default("member"),
15273
+ globalInstruction: text("global_instruction").notNull().default(""),
15270
15274
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
15271
15275
  }, (t) => [unique("member_workspace_user").on(t.workspaceId, t.userId)]);
15272
15276
  var machine = sqliteTable("machine", {
@@ -15372,6 +15376,7 @@ var agentTaskQueue = sqliteTable("agent_task_queue", {
15372
15376
  error: text("error")
15373
15377
  }, (t) => [
15374
15378
  index("idx_task_queue_pending").on(t.agentId, t.status).where(sql`status IN ('queued', 'dispatched')`),
15379
+ index("idx_task_queue_workspace_active").on(t.workspaceId, t.status, t.agentId).where(sql`status IN ('queued', 'dispatched', 'running')`),
15375
15380
  foreignKey({
15376
15381
  columns: [t.agentId, t.workspaceId],
15377
15382
  foreignColumns: [agent.id, agent.workspaceId]
@@ -15527,16 +15532,33 @@ class DaemonClient {
15527
15532
  "Content-Type": "application/json",
15528
15533
  Authorization: `Bearer ${token}`
15529
15534
  };
15530
- const res = await fetch(this.baseURL + path, {
15531
- method,
15532
- headers,
15533
- body: body ? JSON.stringify(body) : undefined
15534
- });
15535
- if (!res.ok)
15536
- throw new Error(`HTTP ${res.status}: ${await res.text()}`);
15537
- if (res.status === 204)
15538
- return;
15539
- return res.json();
15535
+ const MAX_RETRIES = 3;
15536
+ const BASE_DELAY_MS = 500;
15537
+ let lastError;
15538
+ for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
15539
+ try {
15540
+ const res = await fetch(this.baseURL + path, {
15541
+ method,
15542
+ headers,
15543
+ body: body ? JSON.stringify(body) : undefined
15544
+ });
15545
+ if (!res.ok)
15546
+ throw new Error(`HTTP ${res.status}: ${await res.text()}`);
15547
+ if (res.status === 204)
15548
+ return;
15549
+ return res.json();
15550
+ } catch (e) {
15551
+ if (e instanceof TypeError) {
15552
+ lastError = e;
15553
+ if (attempt < MAX_RETRIES) {
15554
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS * 2 ** attempt));
15555
+ continue;
15556
+ }
15557
+ }
15558
+ throw e;
15559
+ }
15560
+ }
15561
+ throw lastError;
15540
15562
  }
15541
15563
  async register(token, body) {
15542
15564
  const raw = await this.request("POST", "/api/daemon/register", token, body);
@@ -15571,11 +15593,28 @@ class DaemonClient {
15571
15593
  return this.request("GET", `/api/artifacts/${artifactId}?workspace_id=${encodeURIComponent(workspaceId)}`, token);
15572
15594
  }
15573
15595
  async downloadArtifact(token, artifactId, workspaceId) {
15574
- const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
15575
- if (!res.ok) {
15576
- throw new Error(`artifact download failed: HTTP ${res.status}`);
15596
+ const MAX_RETRIES = 3;
15597
+ const BASE_DELAY_MS = 500;
15598
+ let lastError;
15599
+ for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
15600
+ try {
15601
+ const res = await fetch(`${this.baseURL}/api/artifacts/${artifactId}/content?workspace_id=${encodeURIComponent(workspaceId)}`, { headers: { Authorization: `Bearer ${token}` } });
15602
+ if (!res.ok) {
15603
+ throw new Error(`artifact download failed: HTTP ${res.status}`);
15604
+ }
15605
+ return res.arrayBuffer();
15606
+ } catch (e) {
15607
+ if (e instanceof TypeError) {
15608
+ lastError = e;
15609
+ if (attempt < MAX_RETRIES) {
15610
+ await new Promise((r) => setTimeout(r, BASE_DELAY_MS * 2 ** attempt));
15611
+ continue;
15612
+ }
15613
+ }
15614
+ throw e;
15615
+ }
15577
15616
  }
15578
- return res.arrayBuffer();
15617
+ throw lastError;
15579
15618
  }
15580
15619
  reportMessages(token, taskId, messages) {
15581
15620
  return this.request("POST", `/api/daemon/tasks/${taskId}/messages`, token, { messages });
@@ -16595,30 +16634,30 @@ ${task.agent?.userEmail ? `Your owner's email address is '${task.agent.userEmail
16595
16634
 
16596
16635
  ### Emails
16597
16636
  ---
16598
- Run 'npx @alook/cli email pull --agent_id ${task.agentId} --status unread' to download unread emails to '/tmp/alook-emails/'.
16599
- Each email is saved to '/tmp/alook-emails/<emailId>/' with:
16637
+ Run 'npx @alook/cli email pull --agent_id ${task.agentId} --workspace ${task.workspaceId} --status unread' to download unread emails to '/tmp/alook-emails/${task.workspaceId}/${task.agentId}/'.
16638
+ Each email is saved to '/tmp/alook-emails/${task.workspaceId}/${task.agentId}/<emailId>/' with:
16600
16639
  - 'metadata.json' — sender, recipient, subject, date, status, message_id, in_reply_to, references
16601
16640
  - 'body.txt' — plain text body
16602
16641
  - 'body.html' — HTML body (if available)
16603
16642
  - 'attachments/' — extracted attachment files (if any)
16604
16643
  ---
16605
16644
  Before starting to process an email, mark it as read:
16606
- - Run 'npx @alook/cli email set --agent_id ${task.agentId} --email_id <EMAIL_ID> --status read'
16645
+ - Run 'npx @alook/cli email set --agent_id ${task.agentId} --workspace ${task.workspaceId} --email_id <EMAIL_ID> --status read'
16607
16646
  ---
16608
16647
 
16609
16648
  #### Sending a new email
16610
16649
  Write the HTML body to a file first, then send it. The body is forwarded as-is (HTML).
16611
- - Run 'npx @alook/cli email send --agent_id ${task.agentId} --to <ADDRESS> --subject "<SUBJECT>" --body-file <PATH_TO_HTML>'
16650
+ - Run 'npx @alook/cli email send --agent_id ${task.agentId} --workspace ${task.workspaceId} --to <ADDRESS> --subject "<SUBJECT>" --body-file <PATH_TO_HTML>'
16612
16651
  - To send from a specific mailbox, add '--from <YOUR_EMAIL_ADDRESS>'. Without '--from', the default Alook address is used.
16613
16652
  - Attach files with '--attachment <PATH>' — repeat the flag for multiple attachments. Each file is uploaded before sending.
16614
- - Example: 'npx @alook/cli email send --agent_id ${task.agentId} --to foo@bar.com --subject "Weekly report" --body-file /tmp/body.html --from alice@company.com --attachment /tmp/report.pdf'
16653
+ - Example: 'npx @alook/cli email send --agent_id ${task.agentId} --workspace ${task.workspaceId} --to foo@bar.com --subject "Weekly report" --body-file /tmp/body.html --from alice@company.com --attachment /tmp/report.pdf'
16615
16654
 
16616
16655
  #### Replying to an email
16617
16656
  To reply to an email, add '--in-reply-to <EMAIL_ID>' to the send command. This sets the correct email threading headers so the recipient's email client groups the reply into the same conversation thread.
16618
16657
  - Use 'Re: <original subject>' as the subject.
16619
16658
  - Quote the original email body in your reply (wrap it in a blockquote).
16620
16659
  - The <EMAIL_ID> is the Alook email id from metadata.json (not the message_id header).
16621
- - Example: 'npx @alook/cli email send --agent_id ${task.agentId} --to sender@example.com --subject "Re: Bug report" --body-file /tmp/reply.html --in-reply-to <EMAIL_ID>'
16660
+ - Example: 'npx @alook/cli email send --agent_id ${task.agentId} --workspace ${task.workspaceId} --to sender@example.com --subject "Re: Bug report" --body-file /tmp/reply.html --in-reply-to <EMAIL_ID>'
16622
16661
  ---
16623
16662
  `;
16624
16663
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Alook CLI — register and run always-on AI coding agents.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",