@alook/cli 0.0.1 → 0.0.2

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +158 -42
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,4 +44,4 @@ Run `alook <command> --help` for subcommand options.
44
44
 
45
45
  ## License
46
46
 
47
- Apache-2.0 — see [LICENSE](./LICENSE).
47
+ Apache-2.0 — see [LICENSE](../../LICENSE).
package/dist/index.js CHANGED
@@ -145,6 +145,137 @@ function saveCLIConfigForProfile(profile, profileConfig) {
145
145
  saveCLIConfig(cfg);
146
146
  }
147
147
 
148
+ // lib/installer.ts
149
+ import { spawnSync } from "child_process";
150
+
151
+ // lib/version.ts
152
+ import { readFileSync as readFileSync2 } from "fs";
153
+ import { join as join2, dirname } from "path";
154
+ import { fileURLToPath } from "url";
155
+ function getCurrentVersion() {
156
+ const __dirname2 = dirname(fileURLToPath(import.meta.url));
157
+ const candidates = [
158
+ join2(__dirname2, "..", "package.json"),
159
+ join2(__dirname2, "..", "..", "package.json")
160
+ ];
161
+ for (const candidate of candidates) {
162
+ try {
163
+ const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
164
+ if (typeof pkg.version === "string")
165
+ return pkg.version;
166
+ } catch {}
167
+ }
168
+ return "unknown";
169
+ }
170
+
171
+ // lib/installer.ts
172
+ var PACKAGE = "@alook/cli";
173
+ var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE}/latest`;
174
+ var FETCH_TIMEOUT_MS = 3000;
175
+ async function fetchLatestVersion() {
176
+ const ctrl = new AbortController;
177
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
178
+ try {
179
+ const res = await fetch(REGISTRY_URL, { signal: ctrl.signal });
180
+ if (!res.ok)
181
+ return null;
182
+ const data = await res.json();
183
+ return typeof data.version === "string" ? data.version : null;
184
+ } catch {
185
+ return null;
186
+ } finally {
187
+ clearTimeout(timer);
188
+ }
189
+ }
190
+ function isNewer(current, latest) {
191
+ const parse = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
192
+ const [cMaj = 0, cMin = 0, cPatch = 0] = parse(current);
193
+ const [lMaj = 0, lMin = 0, lPatch = 0] = parse(latest);
194
+ if (lMaj !== cMaj)
195
+ return lMaj > cMaj;
196
+ if (lMin !== cMin)
197
+ return lMin > cMin;
198
+ return lPatch > cPatch;
199
+ }
200
+ function isNpx() {
201
+ return process.env.npm_command === "exec" || !!process.env.npm_execpath?.includes("npx-cli");
202
+ }
203
+ function detectPackageManager() {
204
+ const ua = process.env.npm_config_user_agent || "";
205
+ if (ua.startsWith("pnpm/"))
206
+ return "pnpm";
207
+ if (ua.startsWith("yarn/"))
208
+ return "yarn";
209
+ return "npm";
210
+ }
211
+ function installArgs(pm) {
212
+ switch (pm) {
213
+ case "pnpm":
214
+ return ["pnpm", ["add", "-g", PACKAGE]];
215
+ case "yarn":
216
+ return ["yarn", ["global", "add", PACKAGE]];
217
+ case "npm":
218
+ return ["npm", ["install", "-g", `${PACKAGE}@latest`]];
219
+ }
220
+ }
221
+ function installCmdString(pm) {
222
+ const [bin, args] = installArgs(pm);
223
+ return `${bin} ${args.join(" ")}`;
224
+ }
225
+ function runInstall(pm) {
226
+ const [bin, args] = installArgs(pm);
227
+ const result = spawnSync(bin, args, { stdio: "inherit" });
228
+ return result.status === 0;
229
+ }
230
+ async function ensureInstalled(opts = {}, deps = {}) {
231
+ const fetchLatest = deps.fetchLatest ?? fetchLatestVersion;
232
+ const runInst = deps.runInstall ?? runInstall;
233
+ const getCurrent = deps.getCurrent ?? getCurrentVersion;
234
+ const isNpxFn = deps.isNpxFn ?? isNpx;
235
+ const isDevFn = deps.isDevFn ?? isDev;
236
+ const log = deps.log ?? ((m) => console.log(m));
237
+ const current = getCurrent();
238
+ const pm = detectPackageManager();
239
+ if (isDevFn() || opts.skip) {
240
+ return { skipped: true, action: "none", current, latest: null, pm };
241
+ }
242
+ const latest = await fetchLatest();
243
+ if (!latest) {
244
+ return { skipped: false, action: "none", current, latest: null, pm };
245
+ }
246
+ const runningViaNpx = isNpxFn();
247
+ const needsInstall = runningViaNpx;
248
+ const needsUpdate = !runningViaNpx && isNewer(current, latest);
249
+ if (!needsInstall && !needsUpdate) {
250
+ log(`
251
+ ✓ ${PACKAGE} is up to date (${current})`);
252
+ return { skipped: false, action: "none", current, latest, pm };
253
+ }
254
+ const cmdStr = installCmdString(pm);
255
+ if (needsInstall) {
256
+ log(`
257
+ Installing ${PACKAGE} globally (${cmdStr})...`);
258
+ } else {
259
+ log(`
260
+ Updating ${PACKAGE} ${current} → ${latest} (${cmdStr})...`);
261
+ }
262
+ const ok = runInst(pm);
263
+ if (!ok) {
264
+ log(`
265
+ Could not ${needsInstall ? "install" : "update"} ${PACKAGE} automatically.`);
266
+ log(`Install it manually: ${cmdStr}`);
267
+ return { skipped: false, action: "failed", current, latest, pm };
268
+ }
269
+ log(`✓ ${needsInstall ? "Installed" : "Updated"} ${PACKAGE} ${latest}`);
270
+ return {
271
+ skipped: false,
272
+ action: needsInstall ? "installed" : "updated",
273
+ current,
274
+ latest,
275
+ pm
276
+ };
277
+ }
278
+
148
279
  // commands/register.ts
149
280
  function isCommandAvailable(cmd) {
150
281
  try {
@@ -168,7 +299,7 @@ function detectRuntimes() {
168
299
  return found;
169
300
  }
170
301
  function registerCommand() {
171
- const cmd = new Command("register").description("Register CLI with your Alook account").requiredOption("--token <token>", "API token (starts with al_)").option("--server <url>", "Server URL").option("--profile <name>", "Profile name").action(async (opts, command) => {
302
+ const cmd = new Command("register").description("Register CLI with your Alook account").requiredOption("--token <token>", "API token (starts with al_)").option("--server <url>", "Server URL").option("--profile <name>", "Profile name").option("--no-install", "Skip auto-install/update of @alook/cli").action(async (opts, command) => {
172
303
  const token = opts.token;
173
304
  const profile = opts.profile || command.parent?.opts().profile;
174
305
  const serverUrl = opts.server || command.parent?.opts().server || process.env.ALOOK_SERVER_URL || "https://alook.ai";
@@ -249,6 +380,7 @@ Usage: ${cmdPrefix()} register --token <token>`);
249
380
  Registered as ${me.email}`);
250
381
  console.log(`Workspace: ${ws.name} (${ws.id})`);
251
382
  console.log(`Runtimes: ${activateResp.runtimes.map((r) => r.provider).join(", ")}`);
383
+ await ensureInstalled({ skip: opts.install === false });
252
384
  console.log();
253
385
  console.log(`Run '${cmdPrefix()} daemon start --foreground' to start the daemon.`);
254
386
  });
@@ -278,7 +410,7 @@ function statusCommand() {
278
410
  import { Command as Command3 } from "commander";
279
411
  import { spawn as spawn2 } from "child_process";
280
412
  import { openSync, closeSync, mkdirSync as mkdirSync3 } from "fs";
281
- import { dirname as dirname3 } from "path";
413
+ import { dirname as dirname4 } from "path";
282
414
 
283
415
  // ../shared/src/constants.ts
284
416
  var POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS) || 3000;
@@ -14518,7 +14650,7 @@ function sql(strings, ...params) {
14518
14650
  return new SQL([new StringChunk(str)]);
14519
14651
  }
14520
14652
  sql2.raw = raw;
14521
- function join2(chunks, separator) {
14653
+ function join3(chunks, separator) {
14522
14654
  const result = [];
14523
14655
  for (const [i, chunk] of chunks.entries()) {
14524
14656
  if (i > 0 && separator !== undefined) {
@@ -14528,7 +14660,7 @@ function sql(strings, ...params) {
14528
14660
  }
14529
14661
  return new SQL(result);
14530
14662
  }
14531
- sql2.join = join2;
14663
+ sql2.join = join3;
14532
14664
  function identifier(value) {
14533
14665
  return new Name(value);
14534
14666
  }
@@ -15581,19 +15713,19 @@ class DaemonClient {
15581
15713
 
15582
15714
  // daemon/config.ts
15583
15715
  import { hostname as hostname4 } from "os";
15584
- import { join as join2 } from "path";
15716
+ import { join as join3 } from "path";
15585
15717
  function pidFilePath(profile) {
15586
15718
  const name = profile ? `daemon_${profile}.pid` : "daemon.pid";
15587
- return join2(configDir(), name);
15719
+ return join3(configDir(), name);
15588
15720
  }
15589
15721
  function daemonLogDir() {
15590
- return join2(configDir(), "daemon", "logs");
15722
+ return join3(configDir(), "daemon", "logs");
15591
15723
  }
15592
15724
  function daemonLogFilePath(date5 = new Date) {
15593
15725
  const y = date5.getFullYear();
15594
15726
  const m = String(date5.getMonth() + 1).padStart(2, "0");
15595
15727
  const d = String(date5.getDate()).padStart(2, "0");
15596
- return join2(daemonLogDir(), `${y}-${m}-${d}.log`);
15728
+ return join3(daemonLogDir(), `${y}-${m}-${d}.log`);
15597
15729
  }
15598
15730
  function parseDuration(s) {
15599
15731
  if (!s)
@@ -15633,7 +15765,7 @@ function loadDaemonConfig(profile) {
15633
15765
  if (profile && !daemonId.endsWith(`-${profile}`)) {
15634
15766
  daemonId = `${daemonId}-${profile}`;
15635
15767
  }
15636
- const defaultRoot = join2(configDir(), profile ? `workspaces_${profile}` : "workspaces");
15768
+ const defaultRoot = join3(configDir(), profile ? `workspaces_${profile}` : "workspaces");
15637
15769
  const workspacesRoot = process.env.ALOOK_WORKSPACES_ROOT || defaultRoot;
15638
15770
  return {
15639
15771
  serverURL: normalizeServerBaseURL(process.env.ALOOK_SERVER_URL || "https://alook.ai"),
@@ -15815,8 +15947,8 @@ function createLogger2(level) {
15815
15947
  var log = createLogger2();
15816
15948
 
15817
15949
  // daemon/pidfile.ts
15818
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
15819
- import { dirname } from "path";
15950
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
15951
+ import { dirname as dirname2 } from "path";
15820
15952
  function isProcessAlive(pid) {
15821
15953
  try {
15822
15954
  process.kill(pid, 0);
@@ -15827,7 +15959,7 @@ function isProcessAlive(pid) {
15827
15959
  }
15828
15960
  function readDaemonPid(profile) {
15829
15961
  try {
15830
- const content = readFileSync2(pidFilePath(profile), "utf-8").trim();
15962
+ const content = readFileSync3(pidFilePath(profile), "utf-8").trim();
15831
15963
  const pid = parseInt(content, 10);
15832
15964
  return Number.isNaN(pid) ? null : pid;
15833
15965
  } catch {
@@ -15837,14 +15969,14 @@ function readDaemonPid(profile) {
15837
15969
  function acquireDaemonPid(profile) {
15838
15970
  const pidPath = pidFilePath(profile);
15839
15971
  try {
15840
- const content = readFileSync2(pidPath, "utf-8").trim();
15972
+ const content = readFileSync3(pidPath, "utf-8").trim();
15841
15973
  const existingPid = parseInt(content, 10);
15842
15974
  if (!isNaN(existingPid) && isProcessAlive(existingPid)) {
15843
15975
  log.error(`Another daemon is already running (PID ${existingPid}). ` + `Remove ${pidPath} if this is stale.`);
15844
15976
  return false;
15845
15977
  }
15846
15978
  } catch {}
15847
- mkdirSync2(dirname(pidPath), { recursive: true, mode: 448 });
15979
+ mkdirSync2(dirname2(pidPath), { recursive: true, mode: 448 });
15848
15980
  writeFileSync2(pidPath, String(process.pid), { mode: 384 });
15849
15981
  return true;
15850
15982
  }
@@ -15863,8 +15995,8 @@ function releaseDaemonPid(profile) {
15863
15995
 
15864
15996
  // daemon/daemon.ts
15865
15997
  import { execSync as execSync3, spawn } from "child_process";
15866
- import { fileURLToPath } from "url";
15867
- import { dirname as dirname2, join as join3 } from "path";
15998
+ import { fileURLToPath as fileURLToPath2 } from "url";
15999
+ import { dirname as dirname3, join as join4 } from "path";
15868
16000
  function isCommandAvailable2(cmd) {
15869
16001
  try {
15870
16002
  execSync3(`which ${cmd}`, { stdio: "ignore" });
@@ -16036,7 +16168,7 @@ async function startDaemon(profile, serverUrl) {
16036
16168
  process.on("SIGINT", shutdown);
16037
16169
  await pollCycle();
16038
16170
  }
16039
- var SESSION_RUNNER_PATH = join3(dirname2(fileURLToPath(import.meta.url)), "session-runner.ts");
16171
+ var SESSION_RUNNER_PATH = join4(dirname3(fileURLToPath2(import.meta.url)), "session-runner.ts");
16040
16172
  function spawnSessionRunner(input) {
16041
16173
  const encoded = Buffer.from(JSON.stringify(input)).toString("base64");
16042
16174
  const child = spawn("bun", ["run", SESSION_RUNNER_PATH, encoded], {
@@ -16114,7 +16246,7 @@ async function startInBackground(profile, serverUrl) {
16114
16246
  return;
16115
16247
  }
16116
16248
  const logPath = daemonLogFilePath();
16117
- mkdirSync3(dirname3(logPath), { recursive: true, mode: 448 });
16249
+ mkdirSync3(dirname4(logPath), { recursive: true, mode: 448 });
16118
16250
  const logFd = openSync(logPath, "a", 384);
16119
16251
  const child = spawn2(process.execPath, buildChildArgs(profile, serverUrl), {
16120
16252
  detached: true,
@@ -16225,7 +16357,7 @@ function configCommand() {
16225
16357
  // commands/email.ts
16226
16358
  import { Command as Command5 } from "commander";
16227
16359
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
16228
- import { join as join4 } from "path";
16360
+ import { join as join5 } from "path";
16229
16361
  import PostalMime from "postal-mime";
16230
16362
  var VALID_STATUSES = ["unread", "read", "archived"];
16231
16363
  var EMAIL_DIR = "/tmp/alook-emails";
@@ -16275,7 +16407,7 @@ function emailCommand() {
16275
16407
  mkdirSync4(EMAIL_DIR, { recursive: true });
16276
16408
  const downloadedPaths = [];
16277
16409
  for (const email3 of emails2) {
16278
- const emailDir = join4(EMAIL_DIR, email3.id);
16410
+ const emailDir = join5(EMAIL_DIR, email3.id);
16279
16411
  mkdirSync4(emailDir, { recursive: true });
16280
16412
  const metadata = {
16281
16413
  id: email3.id,
@@ -16285,7 +16417,7 @@ function emailCommand() {
16285
16417
  date: email3.created_at,
16286
16418
  status: email3.status
16287
16419
  };
16288
- const metadataPath = join4(emailDir, "metadata.json");
16420
+ const metadataPath = join5(emailDir, "metadata.json");
16289
16421
  writeFileSync3(metadataPath, JSON.stringify(metadata, null, 2));
16290
16422
  downloadedPaths.push(metadataPath);
16291
16423
  let rawMime;
@@ -16301,17 +16433,17 @@ function emailCommand() {
16301
16433
  }
16302
16434
  const parsed = await new PostalMime().parse(rawMime);
16303
16435
  if (parsed.text) {
16304
- const bodyPath = join4(emailDir, "body.txt");
16436
+ const bodyPath = join5(emailDir, "body.txt");
16305
16437
  writeFileSync3(bodyPath, parsed.text);
16306
16438
  downloadedPaths.push(bodyPath);
16307
16439
  }
16308
16440
  if (parsed.html) {
16309
- const htmlPath = join4(emailDir, "body.html");
16441
+ const htmlPath = join5(emailDir, "body.html");
16310
16442
  writeFileSync3(htmlPath, parsed.html);
16311
16443
  downloadedPaths.push(htmlPath);
16312
16444
  }
16313
16445
  if (parsed.attachments && parsed.attachments.length > 0) {
16314
- const attDir = join4(emailDir, "attachments");
16446
+ const attDir = join5(emailDir, "attachments");
16315
16447
  mkdirSync4(attDir, { recursive: true });
16316
16448
  const usedFilenames = new Set;
16317
16449
  for (let i = 0;i < parsed.attachments.length; i++) {
@@ -16321,7 +16453,7 @@ function emailCommand() {
16321
16453
  filename = `${i}-${filename}`;
16322
16454
  }
16323
16455
  usedFilenames.add(filename);
16324
- const attPath = join4(attDir, filename);
16456
+ const attPath = join5(attDir, filename);
16325
16457
  const content = att.content;
16326
16458
  let buf;
16327
16459
  if (typeof content === "string") {
@@ -16367,25 +16499,9 @@ function emailCommand() {
16367
16499
 
16368
16500
  // commands/version.ts
16369
16501
  import { Command as Command6 } from "commander";
16370
- import { readFileSync as readFileSync3 } from "fs";
16371
- import { join as join5, dirname as dirname4 } from "path";
16372
- import { fileURLToPath as fileURLToPath2 } from "url";
16373
16502
  function versionCommand() {
16374
16503
  const cmd = new Command6("version").description("Show CLI version").action(() => {
16375
- const __dirname2 = dirname4(fileURLToPath2(import.meta.url));
16376
- let version3 = "unknown";
16377
- try {
16378
- const pkgPath = join5(__dirname2, "..", "package.json");
16379
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
16380
- version3 = pkg.version;
16381
- } catch {
16382
- try {
16383
- const pkgPath = join5(__dirname2, "..", "..", "package.json");
16384
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
16385
- version3 = pkg.version;
16386
- } catch {}
16387
- }
16388
- console.log(`alook version ${version3}`);
16504
+ console.log(`alook version ${getCurrentVersion()}`);
16389
16505
  });
16390
16506
  return cmd;
16391
16507
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alook/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
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",