@alook/daemon 0.0.154 → 0.0.155

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/index.js CHANGED
@@ -7,6 +7,7 @@ import { Command, CommanderError } from "commander";
7
7
 
8
8
  // src/cli/proxyServerApi.ts
9
9
  import * as fs from "fs";
10
+ import * as path from "path";
10
11
  function proxyServerApiFromEnv(prefix = "ALOOK", env = process.env) {
11
12
  const proxyUrl = env[`${prefix}_PROXY_URL`];
12
13
  const tokenFile = env[`${prefix}_PROXY_TOKEN_FILE`];
@@ -37,6 +38,58 @@ function createProxyServerApi(config) {
37
38
  }
38
39
  return json;
39
40
  }
41
+ async function callUpload(req) {
42
+ const form = new FormData;
43
+ const blobType = req.file.contentType ?? "application/octet-stream";
44
+ const bytes = req.file.data instanceof Uint8Array ? new Blob([new Uint8Array(req.file.data)], { type: blobType }) : req.file.data;
45
+ form.append("file", bytes, req.file.filename);
46
+ const url = `${base}/api/attachmentUpload?target=${encodeURIComponent(req.target)}`;
47
+ const res = await fetchImpl(url, {
48
+ method: "POST",
49
+ headers: { authorization: `Bearer ${config.voucher}` },
50
+ body: form
51
+ });
52
+ const json = await res.json();
53
+ if (!res.ok) {
54
+ const e = new Error(json?.error ?? `proxy api/attachmentUpload failed (${res.status})`);
55
+ e.code = json?.code;
56
+ throw e;
57
+ }
58
+ return json;
59
+ }
60
+ async function callDownload(req) {
61
+ const res = await fetchImpl(`${base}/api/attachmentDownload`, {
62
+ method: "POST",
63
+ headers: {
64
+ "content-type": "application/json",
65
+ authorization: `Bearer ${config.voucher}`
66
+ },
67
+ body: JSON.stringify({ id: req.id })
68
+ });
69
+ if (!res.ok) {
70
+ const body = await res.json().catch(() => ({}));
71
+ const e = new Error(body?.error ?? `proxy api/attachmentDownload failed (${res.status})`);
72
+ e.code = body?.code;
73
+ throw e;
74
+ }
75
+ const encoded = res.headers.get("x-alook-filename");
76
+ const filename = encoded ? decodeURIComponent(encoded) : path.basename(req.destPath);
77
+ const contentType = res.headers.get("content-type") || "application/octet-stream";
78
+ const size = Number(res.headers.get("content-length") ?? "0");
79
+ const buf = Buffer.from(await res.arrayBuffer());
80
+ fs.mkdirSync(path.dirname(req.destPath), { recursive: true });
81
+ const tmp = `${req.destPath}.tmp`;
82
+ try {
83
+ fs.writeFileSync(tmp, buf);
84
+ fs.renameSync(tmp, req.destPath);
85
+ } catch (err) {
86
+ try {
87
+ fs.rmSync(tmp, { force: true });
88
+ } catch {}
89
+ throw err;
90
+ }
91
+ return { path: req.destPath, filename, contentType, size: size || buf.byteLength };
92
+ }
40
93
  return {
41
94
  listServers: (r) => call("listServers", r),
42
95
  listChannels: (r) => call("listChannels", r),
@@ -47,13 +100,15 @@ function createProxyServerApi(config) {
47
100
  read: (r) => call("read", r),
48
101
  resolve: (r) => call("resolve", r),
49
102
  listMembers: (r) => call("listMembers", r),
50
- joinServer: (r) => call("joinServer", r)
103
+ joinServer: (r) => call("joinServer", r),
104
+ attachmentUpload: callUpload,
105
+ attachmentDownload: callDownload
51
106
  };
52
107
  }
53
108
 
54
109
  // src/cli/daemonStart.ts
55
110
  import * as fs9 from "fs";
56
- import * as path10 from "path";
111
+ import * as path11 from "path";
57
112
  import * as crypto2 from "crypto";
58
113
  import * as os3 from "os";
59
114
  import { homedir as homedir3 } from "os";
@@ -307,7 +362,7 @@ import * as fs2 from "fs";
307
362
  import * as http from "http";
308
363
  import * as https from "https";
309
364
  import * as os from "os";
310
- import * as path from "path";
365
+ import * as path2 from "path";
311
366
  import { URL } from "url";
312
367
  var DEFAULT_HEADER_NAMES = {
313
368
  agentId: "X-Agent-Id",
@@ -335,15 +390,15 @@ class CredentialBroker {
335
390
  this.voucherPrefix = config.voucherPrefix ?? "vch_";
336
391
  this.clientLabel = config.clientLabel ?? "cli";
337
392
  this.headerNames = config.headerNames ?? DEFAULT_HEADER_NAMES;
338
- this.voucherDir = config.voucherDir ?? path.join(os.tmpdir(), "agent-vouchers");
393
+ this.voucherDir = config.voucherDir ?? path2.join(os.tmpdir(), "agent-vouchers");
339
394
  }
340
395
  mint(agentId, launchId, capabilities, runnerKey) {
341
396
  if (!runnerKey)
342
397
  throw new Error("CredentialBroker.mint: runnerKey is required (per-agent tier-2 credential)");
343
398
  const voucher = randomVoucher(this.voucherPrefix);
344
- const dir = path.join(this.voucherDir, sanitizeIdSegment(agentId));
399
+ const dir = path2.join(this.voucherDir, sanitizeIdSegment(agentId));
345
400
  fs2.mkdirSync(dir, { recursive: true });
346
- const voucherFile = path.join(dir, `${sanitizeIdSegment(launchId)}.token`);
401
+ const voucherFile = path2.join(dir, `${sanitizeIdSegment(launchId)}.token`);
347
402
  fs2.writeFileSync(voucherFile, voucher, { mode: 384 });
348
403
  this.registrations.set(voucher, {
349
404
  agentId,
@@ -402,6 +457,8 @@ function parseBearer(authHeader) {
402
457
  return m ? m[1].trim() : null;
403
458
  }
404
459
  var DEFAULT_CAPABILITY_RESOLVER = (_method, pathname) => {
460
+ if (pathname.includes("/attachment"))
461
+ return "attach";
405
462
  if (pathname.includes("/send"))
406
463
  return "send";
407
464
  if (pathname.includes("/history") || pathname.includes("/search") || pathname.includes("/inbox"))
@@ -429,7 +486,7 @@ async function startCredentialProxy(broker, options = {}) {
429
486
  return;
430
487
  }
431
488
  const reg = verdict.reg;
432
- const isInboxPull = onPull && pathname.endsWith("/inboxPull");
489
+ const isInboxPull = onPull && pathname === "/api/inboxPull";
433
490
  if (onProxyRequest) {
434
491
  try {
435
492
  onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
@@ -1664,7 +1721,7 @@ class AgentRouter {
1664
1721
  }
1665
1722
  }
1666
1723
  // src/timeline/timeline.ts
1667
- import { appendFileSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3, renameSync, existsSync } from "fs";
1724
+ import { appendFileSync, readFileSync as readFileSync3, writeFileSync as writeFileSync4, renameSync as renameSync2, existsSync } from "fs";
1668
1725
  import { join as join2 } from "path";
1669
1726
 
1670
1727
  // src/timeline/filelock.ts
@@ -1790,10 +1847,10 @@ function appendOrMergeEntry(timelineDir, entry, now = new Date) {
1790
1847
  latest.messages = [...latest.messages, ...entry.messages];
1791
1848
  lines[lines.length - 1] = JSON.stringify(latest);
1792
1849
  const tmpPath = join2(timelineDir, `.${filename}.tmp`);
1793
- writeFileSync3(tmpPath, lines.join(`
1850
+ writeFileSync4(tmpPath, lines.join(`
1794
1851
  `) + `
1795
1852
  `);
1796
- renameSync(tmpPath, filePath);
1853
+ renameSync2(tmpPath, filePath);
1797
1854
  return true;
1798
1855
  }
1799
1856
  }
@@ -1830,10 +1887,10 @@ function updateLatestEntry(timelineDir, updater, opts = {}) {
1830
1887
  const entries = lines.map((l) => JSON.parse(l));
1831
1888
  updater(entries[entries.length - 1]);
1832
1889
  const tmpPath = join2(timelineDir, `.${filename}.tmp`);
1833
- writeFileSync3(tmpPath, entries.map((e) => JSON.stringify(e)).join(`
1890
+ writeFileSync4(tmpPath, entries.map((e) => JSON.stringify(e)).join(`
1834
1891
  `) + `
1835
1892
  `);
1836
- renameSync(tmpPath, filePath);
1893
+ renameSync2(tmpPath, filePath);
1837
1894
  return true;
1838
1895
  } catch {} finally {
1839
1896
  releaseLock(lockPath);
@@ -1861,7 +1918,7 @@ function findResumableSession(rows, provider) {
1861
1918
  return null;
1862
1919
  }
1863
1920
  // src/timeline/recorder.ts
1864
- import { mkdirSync as mkdirSync3 } from "fs";
1921
+ import { mkdirSync as mkdirSync4 } from "fs";
1865
1922
  function createTimelineRecorder(opts) {
1866
1923
  const now = opts.now ?? (() => new Date);
1867
1924
  const dirFor = (agentId) => opts.timelineDirFor(agentId);
@@ -1873,7 +1930,7 @@ function createTimelineRecorder(opts) {
1873
1930
  appendEntryForAgent(agentId, messages) {
1874
1931
  const dir = dirFor(agentId);
1875
1932
  try {
1876
- mkdirSync3(dir, { recursive: true });
1933
+ mkdirSync4(dir, { recursive: true });
1877
1934
  } catch {}
1878
1935
  appendOrMergeEntry(dir, createTimelineEntry({
1879
1936
  messages,
@@ -1891,13 +1948,13 @@ function createTimelineRecorder(opts) {
1891
1948
  };
1892
1949
  }
1893
1950
  // src/discovery.ts
1894
- import * as path8 from "path";
1951
+ import * as path9 from "path";
1895
1952
  import * as fs8 from "fs";
1896
1953
  import { fileURLToPath } from "url";
1897
1954
 
1898
1955
  // src/drivers/cliTransport.ts
1899
1956
  import * as fs5 from "fs";
1900
- import * as path3 from "path";
1957
+ import * as path4 from "path";
1901
1958
 
1902
1959
  // src/drivers/systemPrompt.ts
1903
1960
  var CLI = "alook";
@@ -1928,7 +1985,9 @@ function cliCommandsSection() {
1928
1985
  "### Messaging",
1929
1986
  "",
1930
1987
  `1. \`${CLI} inbox pull\` — fetch unread messages.`,
1931
- `2. \`${CLI} message send\` — send a message to a channel, DM, or thread.`,
1988
+ `2. \`${CLI} message send\` — send a message to a channel, DM, or thread. ` + `Attach files with \`--attachment <id>\` (repeatable, order matters).`,
1989
+ `3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a local file; ` + `returns an id. Feed that id into \`message send --attachment <id>\`. ` + `The id is stable across the pending→persisted lifecycle.`,
1990
+ `4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download an attachment ` + `id from any message you have access to (or your own pending uploads).`,
1932
1991
  "",
1933
1992
  "### Servers",
1934
1993
  "",
@@ -2173,21 +2232,21 @@ function resolveLaunchFields(config) {
2173
2232
 
2174
2233
  // src/drivers/cliLink.ts
2175
2234
  import * as fs4 from "fs";
2176
- import * as path2 from "path";
2235
+ import * as path3 from "path";
2177
2236
  function writeCliLink(stateDir, cliName, hostCliPath, platform = process.platform) {
2178
- const binDir = path2.join(stateDir, "bin");
2237
+ const binDir = path3.join(stateDir, "bin");
2179
2238
  fs4.mkdirSync(binDir, { recursive: true });
2180
2239
  if (!hostCliPath)
2181
2240
  return binDir;
2182
2241
  if (platform === "win32") {
2183
- const cmdFile = path2.join(binDir, `${cliName}.cmd`);
2242
+ const cmdFile = path3.join(binDir, `${cliName}.cmd`);
2184
2243
  const body = `@echo off\r
2185
2244
  "${hostCliPath}" %*\r
2186
2245
  `;
2187
2246
  fs4.writeFileSync(cmdFile, body);
2188
2247
  return binDir;
2189
2248
  }
2190
- const linkPath = path2.join(binDir, cliName);
2249
+ const linkPath = path3.join(binDir, cliName);
2191
2250
  try {
2192
2251
  fs4.unlinkSync(linkPath);
2193
2252
  } catch (err) {
@@ -2251,7 +2310,7 @@ function runtimeContextEnv(prefix, rc) {
2251
2310
 
2252
2311
  // src/drivers/agentFile.ts
2253
2312
  import {
2254
- writeFileSync as writeFileSync5,
2313
+ writeFileSync as writeFileSync6,
2255
2314
  readFileSync as readFileSync4,
2256
2315
  lstatSync,
2257
2316
  symlinkSync as symlinkSync2,
@@ -2319,7 +2378,7 @@ function writeAgentFile(workDir, systemPromptContent) {
2319
2378
  const filePath = join4(workDir, CANONICAL_FILE);
2320
2379
  const changed = hasContentChanged(filePath, systemPromptContent);
2321
2380
  if (changed) {
2322
- writeFileSync5(filePath, systemPromptContent, "utf-8");
2381
+ writeFileSync6(filePath, systemPromptContent, "utf-8");
2323
2382
  }
2324
2383
  ensureSymlinks(workDir);
2325
2384
  return changed;
@@ -2342,13 +2401,13 @@ var DEFAULT_CLI_CONFIG = {
2342
2401
  stateDirName: ".alook"
2343
2402
  };
2344
2403
  function resolveStateHome(envPrefix) {
2345
- return process.env[`${envPrefix}_HOME`] || path3.join(process.env.HOME || process.env.USERPROFILE || ".", `.${envPrefix.toLowerCase()}`);
2404
+ return process.env[`${envPrefix}_HOME`] || path4.join(process.env.HOME || process.env.USERPROFILE || ".", `.${envPrefix.toLowerCase()}`);
2346
2405
  }
2347
2406
  async function prepareCliTransport(ctx, extraEnv = {}, cli = DEFAULT_CLI_CONFIG, platform = process.platform) {
2348
2407
  const E = cli.envPrefix;
2349
2408
  const stateHome = resolveStateHome(E);
2350
2409
  const capabilities = cli.activeCapabilities ?? DEFAULT_ACTIVE_CAPABILITIES;
2351
- const stateDir = path3.join(ctx.workingDirectory, cli.stateDirName);
2410
+ const stateDir = path4.join(ctx.workingDirectory, cli.stateDirName);
2352
2411
  await fs5.promises.mkdir(stateDir, { recursive: true });
2353
2412
  if (ctx.standingPrompt)
2354
2413
  writeAgentFile(ctx.workingDirectory, ctx.standingPrompt);
@@ -2360,7 +2419,7 @@ async function prepareCliTransport(ctx, extraEnv = {}, cli = DEFAULT_CLI_CONFIG,
2360
2419
  const reg = ctx.credentialProxy.broker.mint(ctx.agentId, ctx.launchId ?? "default", capabilities, ctx.credentialProxy.runnerKey);
2361
2420
  const tokenFile = reg.voucherFile;
2362
2421
  const resolved = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
2363
- const pathValue = [binDir, process.env.PATH ?? ""].filter(Boolean).join(path3.delimiter);
2422
+ const pathValue = [binDir, process.env.PATH ?? ""].filter(Boolean).join(path4.delimiter);
2364
2423
  const layers = [
2365
2424
  { name: "hostStatic", precedence: 10, vars: cli.extraEnv ?? {} },
2366
2425
  { name: "userEnv", precedence: 20, vars: resolved.envVars },
@@ -2404,19 +2463,19 @@ function buildCliTransportSystemPrompt(config, opts) {
2404
2463
 
2405
2464
  // src/drivers/claudeProviderIsolation.ts
2406
2465
  import * as fs6 from "fs";
2407
- import * as path4 from "path";
2466
+ import * as path5 from "path";
2408
2467
  function buildClaudeProviderIsolationEnv(ctx) {
2409
2468
  const hasCustomProvider = Boolean(process.env.ANTHROPIC_BASE_URL && process.env.ANTHROPIC_API_KEY);
2410
2469
  if (!hasCustomProvider)
2411
2470
  return {};
2412
- const root = path4.join(ctx.workingDirectory, ".alook", "claude-provider");
2413
- const home = path4.join(root, "home");
2414
- const configDir = path4.join(home, ".claude");
2471
+ const root = path5.join(ctx.workingDirectory, ".alook", "claude-provider");
2472
+ const home = path5.join(root, "home");
2473
+ const configDir = path5.join(home, ".claude");
2415
2474
  fs6.mkdirSync(configDir, { recursive: true });
2416
- const hostClaude = path4.join(process.env.HOME || ".", ".claude");
2475
+ const hostClaude = path5.join(process.env.HOME || ".", ".claude");
2417
2476
  for (const sub of ["skills", "commands"]) {
2418
- const target = path4.join(hostClaude, sub);
2419
- const link = path4.join(configDir, sub);
2477
+ const target = path5.join(hostClaude, sub);
2478
+ const link = path5.join(configDir, sub);
2420
2479
  try {
2421
2480
  if (fs6.existsSync(target) && !fs6.existsSync(link))
2422
2481
  fs6.symlinkSync(target, link);
@@ -2433,7 +2492,7 @@ function buildClaudeProviderIsolationEnv(ctx) {
2433
2492
  // src/drivers/probe.ts
2434
2493
  import { execFileSync } from "child_process";
2435
2494
  import * as fs7 from "fs";
2436
- import * as path5 from "path";
2495
+ import * as path6 from "path";
2437
2496
  function resolveCommandOnPath(command, deps = {}) {
2438
2497
  if (deps.which)
2439
2498
  return deps.which(command);
@@ -2474,7 +2533,7 @@ function probeCommandVersion(command, args = [], deps = {}, platform = process.p
2474
2533
  }
2475
2534
  }
2476
2535
  function resolveHomePath(relativePath, deps = {}) {
2477
- return path5.join(deps.homeDir || process.env.HOME || ".", relativePath);
2536
+ return path6.join(deps.homeDir || process.env.HOME || ".", relativePath);
2478
2537
  }
2479
2538
  function resolveSpawnSpec(command, args, deps = {}, platform = process.platform) {
2480
2539
  const resolved = resolveCommandOnPath(command, deps) ?? command;
@@ -2885,7 +2944,7 @@ class CodexEventNormalizer {
2885
2944
 
2886
2945
  // src/drivers/codexHome.ts
2887
2946
  import * as os2 from "os";
2888
- import * as path6 from "path";
2947
+ import * as path7 from "path";
2889
2948
  function readConfiguredCodexHome(env) {
2890
2949
  const raw = env.CODEX_HOME;
2891
2950
  return typeof raw === "string" && raw.trim().length > 0 ? raw : null;
@@ -2893,8 +2952,8 @@ function readConfiguredCodexHome(env) {
2893
2952
  function resolveCodexHomeRootFromEnv(env = process.env, opts = {}) {
2894
2953
  const raw = readConfiguredCodexHome(env);
2895
2954
  if (raw)
2896
- return path6.resolve(opts.cwd ?? process.cwd(), raw);
2897
- return path6.join(opts.defaultHomeDir ?? os2.homedir(), ".codex");
2955
+ return path7.resolve(opts.cwd ?? process.cwd(), raw);
2956
+ return path7.join(opts.defaultHomeDir ?? os2.homedir(), ".codex");
2898
2957
  }
2899
2958
 
2900
2959
  // src/drivers/codex.ts
@@ -3557,8 +3616,8 @@ class KimiDriver {
3557
3616
 
3558
3617
  // src/drivers/pi.ts
3559
3618
  import { createRequire as createRequire2 } from "module";
3560
- import { mkdirSync as mkdirSync6, existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "fs";
3561
- import * as path7 from "path";
3619
+ import { mkdirSync as mkdirSync7, existsSync as existsSync5, readFileSync as readFileSync5, realpathSync } from "fs";
3620
+ import * as path8 from "path";
3562
3621
 
3563
3622
  // src/runtime/sdkRuntimeSession.ts
3564
3623
  import { EventEmitter as EventEmitter3 } from "events";
@@ -3651,15 +3710,15 @@ function resolvePiSdkPackageDir(deps = {}) {
3651
3710
  if (!binPath)
3652
3711
  return;
3653
3712
  try {
3654
- let dir = path7.dirname(realpathSync(binPath));
3713
+ let dir = path8.dirname(realpathSync(binPath));
3655
3714
  const MAX_DEPTH = 8;
3656
3715
  for (let i = 0;i < MAX_DEPTH; i++) {
3657
- if (isPiSdkPackageJson(path7.join(dir, "package.json")))
3716
+ if (isPiSdkPackageJson(path8.join(dir, "package.json")))
3658
3717
  return dir;
3659
- const siblingDir = path7.join(dir, "node_modules", PI_SDK_PACKAGE_NAME);
3660
- if (isPiSdkPackageJson(path7.join(siblingDir, "package.json")))
3718
+ const siblingDir = path8.join(dir, "node_modules", PI_SDK_PACKAGE_NAME);
3719
+ if (isPiSdkPackageJson(path8.join(siblingDir, "package.json")))
3661
3720
  return siblingDir;
3662
- const parent = path7.dirname(dir);
3721
+ const parent = path8.dirname(dir);
3663
3722
  if (parent === dir)
3664
3723
  break;
3665
3724
  dir = parent;
@@ -3672,7 +3731,7 @@ function resolvePiSdkVersionFromPath(deps = {}) {
3672
3731
  if (!dir)
3673
3732
  return;
3674
3733
  try {
3675
- const pkg = JSON.parse(readFileSync5(path7.join(dir, "package.json"), "utf-8"));
3734
+ const pkg = JSON.parse(readFileSync5(path8.join(dir, "package.json"), "utf-8"));
3676
3735
  return pkg.version;
3677
3736
  } catch {
3678
3737
  return;
@@ -3745,7 +3804,7 @@ class PiDriver {
3745
3804
  async createSession(ctx, deps) {
3746
3805
  const spawnEnv = await deps.buildSpawnEnv();
3747
3806
  if (ctx.standingPrompt) {
3748
- mkdirSync6(ctx.workingDirectory, { recursive: true });
3807
+ mkdirSync7(ctx.workingDirectory, { recursive: true });
3749
3808
  writeAgentFile(ctx.workingDirectory, ctx.standingPrompt);
3750
3809
  }
3751
3810
  const f = resolveLaunchFieldsOrDefault(ctx.config.runtimeConfig);
@@ -3811,22 +3870,22 @@ function listRuntimeIds() {
3811
3870
 
3812
3871
  // src/discovery.ts
3813
3872
  function resolveAlookCliPath(moduleDir) {
3814
- const thisDir = moduleDir ?? path8.dirname(fileURLToPath(import.meta.url));
3815
- const target = path8.basename(thisDir) === "dist" ? path8.resolve(thisDir, "cli", "index.js") : path8.resolve(thisDir, "..", "scripts", "alook-shim.mjs");
3873
+ const thisDir = moduleDir ?? path9.dirname(fileURLToPath(import.meta.url));
3874
+ const target = path9.basename(thisDir) === "dist" ? path9.resolve(thisDir, "cli", "index.js") : path9.resolve(thisDir, "..", "scripts", "alook-shim.mjs");
3816
3875
  return fs8.existsSync(target) ? target : null;
3817
3876
  }
3818
3877
  function deriveCliFallbackCandidates(cliPath) {
3819
3878
  if (!cliPath)
3820
3879
  return [];
3821
- const normalized = cliPath.split(path8.sep).join("/");
3880
+ const normalized = cliPath.split(path9.sep).join("/");
3822
3881
  const marker = "/node_modules/";
3823
3882
  const idx = normalized.indexOf(marker);
3824
3883
  if (idx === -1)
3825
3884
  return [];
3826
3885
  const globalRoot = cliPath.slice(0, idx + marker.length - 1);
3827
- const tail = path8.join("dist", "cli", "index.js");
3886
+ const tail = path9.join("dist", "cli", "index.js");
3828
3887
  return [
3829
- path8.join(globalRoot, "@alook", "daemon", tail)
3888
+ path9.join(globalRoot, "@alook", "daemon", tail)
3830
3889
  ].filter((candidate) => candidate !== cliPath);
3831
3890
  }
3832
3891
  function resolveAlookCliPathWithFallback(primary) {
@@ -3872,7 +3931,7 @@ async function detectRuntimes() {
3872
3931
 
3873
3932
  // src/drivers/piSdkDeps.ts
3874
3933
  import { readFileSync as readFileSync6 } from "fs";
3875
- import * as path9 from "path";
3934
+ import * as path10 from "path";
3876
3935
  import { pathToFileURL } from "url";
3877
3936
  var PI_SDK_PACKAGE_NAME2 = "@earendil-works/pi-coding-agent";
3878
3937
  var cachedSdkPromise = null;
@@ -3881,9 +3940,9 @@ async function importPiSdkFromGlobalInstall() {
3881
3940
  if (!dir) {
3882
3941
  throw new Error(`${PI_SDK_PACKAGE_NAME2} not found — install it (e.g. \`npm install -g ${PI_SDK_PACKAGE_NAME2}\`) before launching a pi agent`);
3883
3942
  }
3884
- const pkg = JSON.parse(readFileSync6(path9.join(dir, "package.json"), "utf-8"));
3943
+ const pkg = JSON.parse(readFileSync6(path10.join(dir, "package.json"), "utf-8"));
3885
3944
  const entry = pkg.exports?.["."]?.import ?? pkg.main ?? "./dist/index.js";
3886
- const entryPath = path9.join(dir, entry);
3945
+ const entryPath = path10.join(dir, entry);
3887
3946
  return import(pathToFileURL(entryPath).href);
3888
3947
  }
3889
3948
  function loadPiSdkModule() {
@@ -4237,10 +4296,10 @@ function readDaemonVersion() {
4237
4296
  return "";
4238
4297
  }
4239
4298
  }
4240
- var CAPABILITIES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
4299
+ var CAPABILITIES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge", "attach"];
4241
4300
  function resolveDefaultBaseDir() {
4242
- const root = process.env.ALOOK_PROJECT_ROOT || path10.join(homedir3(), ".alook");
4243
- return path10.join(root, "daemon");
4301
+ const root = process.env.ALOOK_PROJECT_ROOT || path11.join(homedir3(), ".alook");
4302
+ return path11.join(root, "daemon");
4244
4303
  }
4245
4304
  var DEFAULT_BASE_DIR = resolveDefaultBaseDir();
4246
4305
  var log = createLogger({ header: "@alook/daemon" });
@@ -4248,10 +4307,10 @@ function keyHash(machineKey) {
4248
4307
  return crypto2.createHash("sha256").update(machineKey).digest("hex").slice(0, 12);
4249
4308
  }
4250
4309
  function daemonsDir(baseDir) {
4251
- return path10.join(baseDir, "daemons");
4310
+ return path11.join(baseDir, "daemons");
4252
4311
  }
4253
4312
  function pidfilePath(baseDir, machineKey) {
4254
- return path10.join(daemonsDir(baseDir), `${keyHash(machineKey)}.pid`);
4313
+ return path11.join(daemonsDir(baseDir), `${keyHash(machineKey)}.pid`);
4255
4314
  }
4256
4315
  function isProcessAlive(pid) {
4257
4316
  try {
@@ -4272,7 +4331,7 @@ function readPidFile(filePath) {
4272
4331
  return null;
4273
4332
  }
4274
4333
  function writePidFile(filePath, pid, machineKey) {
4275
- fs9.mkdirSync(path10.dirname(filePath), { recursive: true });
4334
+ fs9.mkdirSync(path11.dirname(filePath), { recursive: true });
4276
4335
  fs9.writeFileSync(filePath, JSON.stringify({ pid, key: machineKey }));
4277
4336
  }
4278
4337
  function acquireLock2(baseDir, machineKey) {
@@ -4301,7 +4360,7 @@ function daemonList(opts) {
4301
4360
  const files = fs9.readdirSync(dir).filter((f) => f.endsWith(".pid"));
4302
4361
  const results = [];
4303
4362
  for (const file of files) {
4304
- const filePath = path10.join(dir, file);
4363
+ const filePath = path11.join(dir, file);
4305
4364
  const data = readPidFile(filePath);
4306
4365
  if (!data)
4307
4366
  continue;
@@ -4353,7 +4412,7 @@ function daemonStop(opts) {
4353
4412
  } catch {}
4354
4413
  }
4355
4414
  function credentialFilePathByMachineId(baseDir, machineId) {
4356
- return path10.join(daemonsDir(baseDir), `${machineId}.credential.json`);
4415
+ return path11.join(daemonsDir(baseDir), `${machineId}.credential.json`);
4357
4416
  }
4358
4417
  function credentialFilesDir(baseDir) {
4359
4418
  return daemonsDir(baseDir);
@@ -4370,7 +4429,7 @@ function readCredentialFile(filePath) {
4370
4429
  return null;
4371
4430
  }
4372
4431
  function writeCredentialFile(filePath, credential, machineId) {
4373
- fs9.mkdirSync(path10.dirname(filePath), { recursive: true });
4432
+ fs9.mkdirSync(path11.dirname(filePath), { recursive: true });
4374
4433
  fs9.writeFileSync(filePath, JSON.stringify({ credential, machineId }), { mode: 384 });
4375
4434
  }
4376
4435
  function findExistingCredentialForBearer(baseDir, bearer) {
@@ -4380,7 +4439,7 @@ function findExistingCredentialForBearer(baseDir, bearer) {
4380
4439
  for (const file of fs9.readdirSync(dir)) {
4381
4440
  if (!file.endsWith(".credential.json"))
4382
4441
  continue;
4383
- const parsed = readCredentialFile(path10.join(dir, file));
4442
+ const parsed = readCredentialFile(path11.join(dir, file));
4384
4443
  if (parsed && parsed.credential === bearer)
4385
4444
  return parsed;
4386
4445
  }
@@ -4549,6 +4608,50 @@ function agentId(opts) {
4549
4608
  throw new CliError("agent identity required — pass --agent <id> or set ALOOK_AGENT_ID");
4550
4609
  return id;
4551
4610
  }
4611
+ var CLIENT_MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
4612
+ var CLIENT_ALLOWED_MIME_PREFIXES = [
4613
+ "image/",
4614
+ "video/",
4615
+ "audio/",
4616
+ "text/",
4617
+ "application/pdf",
4618
+ "application/json",
4619
+ "application/zip",
4620
+ "application/octet-stream"
4621
+ ];
4622
+ function mimeAllowed(contentType) {
4623
+ if (!contentType)
4624
+ return false;
4625
+ return CLIENT_ALLOWED_MIME_PREFIXES.some((entry) => entry.endsWith("/") ? contentType.startsWith(entry) : contentType === entry);
4626
+ }
4627
+ function contentTypeFromFilename(filename) {
4628
+ const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
4629
+ switch (ext) {
4630
+ case "png":
4631
+ return "image/png";
4632
+ case "jpg":
4633
+ case "jpeg":
4634
+ return "image/jpeg";
4635
+ case "gif":
4636
+ return "image/gif";
4637
+ case "webp":
4638
+ return "image/webp";
4639
+ case "svg":
4640
+ return "image/svg+xml";
4641
+ case "pdf":
4642
+ return "application/pdf";
4643
+ case "txt":
4644
+ case "md":
4645
+ case "log":
4646
+ return "text/plain";
4647
+ case "json":
4648
+ return "application/json";
4649
+ case "zip":
4650
+ return "application/zip";
4651
+ default:
4652
+ return "application/octet-stream";
4653
+ }
4654
+ }
4552
4655
  async function cmdMessageSend(opts) {
4553
4656
  const api = getApi();
4554
4657
  const agent = agentId(opts);
@@ -4566,15 +4669,81 @@ async function cmdMessageSend(opts) {
4566
4669
  } else if (typeof textFlag === "string") {
4567
4670
  text = textFlag;
4568
4671
  }
4569
- if (!text) {
4570
- throw new CliError("message send: --text <text> or --file <path> is required");
4672
+ const attachmentIds = Array.isArray(opts.attachment) ? opts.attachment : [];
4673
+ const hasText = typeof text === "string" && text.trim().length > 0;
4674
+ if (!hasText && attachmentIds.length === 0) {
4675
+ throw new CliError("message send: --text <text>, --file <path>, or --attachment <id> is required");
4571
4676
  }
4572
- const res = await api.send({ agentId: agent, channel, content: { text } });
4677
+ const res = await api.send({
4678
+ agentId: agent,
4679
+ channel,
4680
+ content: { text: text ?? "" },
4681
+ attachments: attachmentIds.length > 0 ? attachmentIds : undefined
4682
+ });
4573
4683
  if (res.state === "blocked") {
4574
4684
  throw new CliError(`channel not aligned: ${res.unreadCount} unread message(s) in ${channel} (latest #${res.latestSeq}). Run \`alook inbox pull\` to align, then resend.`);
4575
4685
  }
4576
4686
  return { sent: `${res.message.channel}${res.message.seq}` };
4577
4687
  }
4688
+ async function cmdAttachmentUpload(opts) {
4689
+ const api = getApi();
4690
+ const agent = agentId(opts);
4691
+ const target = opts.target;
4692
+ const filePath = opts.file;
4693
+ if (!target)
4694
+ throw new CliError("message attachment upload: --target <ref> is required");
4695
+ if (!filePath)
4696
+ throw new CliError("message attachment upload: --file <path> is required");
4697
+ const fs10 = await import("fs/promises");
4698
+ let bytes;
4699
+ try {
4700
+ bytes = await fs10.readFile(filePath);
4701
+ } catch (err) {
4702
+ throw new CliError(`message attachment upload: cannot read file: ${err.message}`);
4703
+ }
4704
+ if (bytes.byteLength > CLIENT_MAX_ATTACHMENT_BYTES) {
4705
+ throw new CliError(`message attachment upload: file too large — ${bytes.byteLength} bytes, max ${CLIENT_MAX_ATTACHMENT_BYTES}`);
4706
+ }
4707
+ const pathMod = await import("path");
4708
+ const filename = pathMod.basename(filePath);
4709
+ const contentType = contentTypeFromFilename(filename);
4710
+ if (!mimeAllowed(contentType)) {
4711
+ throw new CliError(`message attachment upload: content type not allowed: ${contentType}`);
4712
+ }
4713
+ const result = await api.attachmentUpload({
4714
+ agentId: agent,
4715
+ target,
4716
+ file: { data: new Uint8Array(bytes), filename, contentType }
4717
+ });
4718
+ return result;
4719
+ }
4720
+ async function cmdAttachmentDownload(opts) {
4721
+ const api = getApi();
4722
+ const agent = agentId(opts);
4723
+ const id = opts.id;
4724
+ if (!id)
4725
+ throw new CliError("message attachment download: --id <id> is required");
4726
+ const outFlag = opts.out;
4727
+ const os4 = await import("os");
4728
+ const pathMod = await import("path");
4729
+ const destPath = outFlag ?? pathMod.join(os4.tmpdir(), "alook-attachments", agent, id, "file");
4730
+ const result = await api.attachmentDownload({ agentId: agent, id, destPath });
4731
+ if (!outFlag) {
4732
+ const fs10 = await import("fs/promises");
4733
+ const destDir = pathMod.dirname(destPath);
4734
+ const safeName = pathMod.basename(result.filename) || "file";
4735
+ const renamed = pathMod.join(destDir, safeName);
4736
+ if (renamed !== destPath) {
4737
+ try {
4738
+ await fs10.rename(destPath, renamed);
4739
+ return { ...result, path: renamed };
4740
+ } catch {
4741
+ return { ...result, path: destPath };
4742
+ }
4743
+ }
4744
+ }
4745
+ return result;
4746
+ }
4578
4747
  async function cmdInboxPull(opts) {
4579
4748
  const api = getApi();
4580
4749
  const agent = agentId(opts);
@@ -4654,12 +4823,26 @@ function buildProgram() {
4654
4823
  }).option("--agent <id>", "agent identity (or ALOOK_AGENT_ID env)");
4655
4824
  const message = program.command("message").description("message operations").exitOverride();
4656
4825
  message.configureOutput({ writeOut: () => {}, writeErr: () => {} });
4657
- message.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace/general)").option("--text <text>", "inline message body (short messages)").option("--file <path>", "read message body from a file (long messages)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
4826
+ message.command("send").description("send a message to a channel, DM, or thread").option("--target <ref>", "destination (path-style ref, e.g. /demo-workspace/general)").option("--text <text>", "inline message body (short messages)").option("--file <path>", "read message body from a file (long messages)").option("-a, --attachment <id>", "attach an uploaded file by id (repeatable — order = message order)", (v, prev = []) => [...prev, v], []).exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
4658
4827
  const localOpts = this.opts();
4659
4828
  const globalOpts = program.opts();
4660
4829
  const result = await cmdMessageSend({ ...globalOpts, ...localOpts });
4661
4830
  printEnvelope({ success: result });
4662
4831
  });
4832
+ const attachment = message.command("attachment").description("attachment operations").exitOverride();
4833
+ attachment.configureOutput({ writeOut: () => {}, writeErr: () => {} });
4834
+ attachment.command("upload").description("upload a local file as a pending attachment for a future send").option("--target <ref>", "destination (channel, DM, or thread ref)").option("--file <path>", "local file to upload").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
4835
+ const localOpts = this.opts();
4836
+ const globalOpts = program.opts();
4837
+ const result = await cmdAttachmentUpload({ ...globalOpts, ...localOpts });
4838
+ printEnvelope({ success: result });
4839
+ });
4840
+ attachment.command("download").description("download an attachment by id to disk").option("--id <id>", "attachment id (from inbox pull / send response)").option("--out <path>", "explicit output path (default: /tmp/alook-attachments/<agent>/<id>/<filename>)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
4841
+ const localOpts = this.opts();
4842
+ const globalOpts = program.opts();
4843
+ const result = await cmdAttachmentDownload({ ...globalOpts, ...localOpts });
4844
+ printEnvelope({ success: result });
4845
+ });
4663
4846
  const inbox = program.command("inbox").description("inbox operations").exitOverride();
4664
4847
  inbox.configureOutput({ writeOut: () => {}, writeErr: () => {} });
4665
4848
  inbox.command("pull").description("fetch unread messages from all channels").option("--max <n>", "max messages to return").option("--no-ack", "do not advance read waterlines (peek only)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
package/dist/index.js CHANGED
@@ -31,7 +31,9 @@ function cliCommandsSection() {
31
31
  "### Messaging",
32
32
  "",
33
33
  `1. \`${CLI} inbox pull\` — fetch unread messages.`,
34
- `2. \`${CLI} message send\` — send a message to a channel, DM, or thread.`,
34
+ `2. \`${CLI} message send\` — send a message to a channel, DM, or thread. ` + `Attach files with \`--attachment <id>\` (repeatable, order matters).`,
35
+ `3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a local file; ` + `returns an id. Feed that id into \`message send --attachment <id>\`. ` + `The id is stable across the pending→persisted lifecycle.`,
36
+ `4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download an attachment ` + `id from any message you have access to (or your own pending uploads).`,
35
37
  "",
36
38
  "### Servers",
37
39
  "",
@@ -4093,6 +4095,8 @@ function parseBearer(authHeader) {
4093
4095
  return m ? m[1].trim() : null;
4094
4096
  }
4095
4097
  var DEFAULT_CAPABILITY_RESOLVER = (_method, pathname) => {
4098
+ if (pathname.includes("/attachment"))
4099
+ return "attach";
4096
4100
  if (pathname.includes("/send"))
4097
4101
  return "send";
4098
4102
  if (pathname.includes("/history") || pathname.includes("/search") || pathname.includes("/inbox"))
@@ -4120,7 +4124,7 @@ async function startCredentialProxy(broker, options = {}) {
4120
4124
  return;
4121
4125
  }
4122
4126
  const reg = verdict.reg;
4123
- const isInboxPull = onPull && pathname.endsWith("/inboxPull");
4127
+ const isInboxPull = onPull && pathname === "/api/inboxPull";
4124
4128
  if (onProxyRequest) {
4125
4129
  try {
4126
4130
  onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/daemon",
3
- "version": "0.0.154",
3
+ "version": "0.0.155",
4
4
  "description": "Alook agent daemon — host-side runtime backend, process manager, credential proxy, and control plane.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/alookai/alook#readme",