arnesa 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -39,7 +39,7 @@ Add `--json` for one JSON document on stdout.
39
39
 
40
40
  ## Limits
41
41
 
42
- Working defaults, effective 2026-11-06. They can change, and the server answers with exit code 5 when one is reached.
42
+ Working defaults, effective 2026-11-06, confirmed by the founder on 2026-11-07 (Open Question 1, design decision 6 of `add-arnesa-local-cli`). They can change, and the server answers with exit code 5 when one is reached.
43
43
 
44
44
  | Caller | `recommend` | `compile` (install) |
45
45
  | --------------------------------- | ----------------- | ------------------- |
package/dist/arnesa.mjs CHANGED
@@ -20285,7 +20285,8 @@ var messages = {
20285
20285
  " remove <harness> Undo one install exactly, using arnesa.lock",
20286
20286
  " doctor Scan the agent configuration files of this project (asks before uploading)",
20287
20287
  " login Sign in with your browser (stores a token in the OS keychain)",
20288
- " logout Remove the stored token from the OS keychain",
20288
+ " login --key Read an API key (ak_) from standard input and store it in the keychain",
20289
+ " logout Sign out: revoke this device's token on the server and remove the stored credential from this machine's keychain. If the revoke fails, the credential is still removed; revoke a device or key at /me.",
20289
20290
  " config telemetry [on|off] Show or change metadata sharing for your account",
20290
20291
  "",
20291
20292
  "Options:",
@@ -20312,7 +20313,12 @@ var messages = {
20312
20313
  loginPrompt: (uri, code2) => `Open ${uri} and enter the code ${code2}`,
20313
20314
  loginLink: (uri) => `Or open this link: ${uri}`,
20314
20315
  loggedIn: "Signed in. The token is stored in your OS keychain.",
20315
- loggedOut: "Signed out on this machine. Revoke the device at /me to invalidate the token.",
20316
+ logoutRevoked: "Signed out and revoked this device.",
20317
+ logoutAlreadyRevoked: "Signed out on this machine. This device had already been revoked, so nothing changed on the server.",
20318
+ logoutRevokeFailed: "Signed out on this machine. The device token could not be revoked (the server failed or was unreachable); revoke this device at your Arnesa /me page.",
20319
+ logoutKeyOnly: "Signed out on this machine. The Arnesa key stays valid until you revoke it at your Arnesa /me page.",
20320
+ logoutNothingStored: 'Nothing is stored on this machine, so there is nothing to sign out of. Run "arnesa login" to sign in.',
20321
+ logoutEnvKeyUntouched: "The ARNESA_API_KEY environment variable was not touched; unset it yourself if you want it gone.",
20316
20322
  loginDenied: "The sign-in was denied.",
20317
20323
  loginExpired: 'The sign-in code expired. Run "arnesa login" again.',
20318
20324
  notLoggedIn: 'Not signed in. Run "arnesa login" or set ARNESA_API_KEY.',
@@ -20369,6 +20375,142 @@ var messages = {
20369
20375
  doctorDone: (status, critical, warning, info) => `Scan ${status}: ${critical} critical, ${warning} warning, ${info} info.`,
20370
20376
  doctorStillRunning: (url2) => `The scan is still running. Check the report later: ${url2}`
20371
20377
  };
20378
+ var commandHelp = {
20379
+ detect: [
20380
+ 'detect \u2014 print the exact profile "recommend" would send. Nothing is sent and no network is used.',
20381
+ "",
20382
+ "Usage: arnesa detect [--runtime <id>] [--model <name>] [--json]",
20383
+ "",
20384
+ "Options:",
20385
+ " --runtime <id> Profile as this runtime instead of the one detected",
20386
+ " --model <name> Profile as this model instead of $ARNESA_MODEL",
20387
+ " --json Print the profile as one JSON document",
20388
+ "",
20389
+ "Exit codes:",
20390
+ " 0 the profile was printed",
20391
+ " 2 this project could not be profiled (no supported manifest found)"
20392
+ ],
20393
+ recommend: [
20394
+ "recommend \u2014 ask Arnesa which verified harnesses fit this project.",
20395
+ "",
20396
+ "The request body is shown before anything is sent. Needs an API URL: --api <url> or $ARNESA_API_URL.",
20397
+ "",
20398
+ "Usage: arnesa recommend [--yes] [--runtime <id>] [--model <name>] [--json]",
20399
+ "",
20400
+ "Options:",
20401
+ " --yes Send the profile without asking",
20402
+ " --runtime <id> Profile as this runtime instead of the one detected",
20403
+ " --model <name> Profile as this model instead of $ARNESA_MODEL",
20404
+ " --json Print the result as one JSON document",
20405
+ "",
20406
+ "Exit codes:",
20407
+ " 0 the recommendations were printed",
20408
+ " 1 the request failed",
20409
+ " 2 no API URL is set, or the project could not be profiled",
20410
+ " 3 confirmation is required and there is no terminal",
20411
+ " 5 rate limited; try again later"
20412
+ ],
20413
+ install: [
20414
+ "install \u2014 install a harness into this project after showing every file it will write.",
20415
+ "",
20416
+ "The server answer is checked and previewed; files are written only after you confirm, all or nothing, and every write is backed up under .arnesa/backups/. A command that starts a process is installed only when you name its gate with --allow.",
20417
+ "",
20418
+ "Usage: arnesa install <harness>[@version] [--runtime <id>] [--yes] [--allow <id>]",
20419
+ "",
20420
+ "Options:",
20421
+ " --runtime <id> The runtime to install for (taken from the project profile when omitted)",
20422
+ " --yes Write the files without asking",
20423
+ " --allow <id> Also install the command gate named <id> (repeatable)",
20424
+ " --json Print the result as one JSON document",
20425
+ "",
20426
+ "Exit codes:",
20427
+ " 0 the harness was installed",
20428
+ " 1 the install failed, the harness is already installed, or the lock is invalid; nothing was written",
20429
+ " 2 wrong usage, or no runtime could be determined",
20430
+ " 3 confirmation is required and there is no terminal",
20431
+ " 4 refused by policy (paid harness, not installable, content changed)",
20432
+ " 5 rate limited"
20433
+ ],
20434
+ remove: [
20435
+ "remove \u2014 undo one install exactly, using arnesa.lock and the backups under .arnesa/backups/.",
20436
+ "",
20437
+ "A file that changed since the install is shown as a diff and removed only after a yes.",
20438
+ "",
20439
+ "Usage: arnesa remove <harness> [--yes] [--json]",
20440
+ "",
20441
+ "Options:",
20442
+ " --yes Remove without asking when files were edited since the install",
20443
+ " --json Print the result as one JSON document",
20444
+ "",
20445
+ "Exit codes:",
20446
+ " 0 the harness was removed",
20447
+ " 1 the lock is invalid, or the harness is not installed in this project",
20448
+ " 2 wrong usage (a harness name is required)",
20449
+ " 3 confirmation is required and there is no terminal"
20450
+ ],
20451
+ doctor: [
20452
+ "doctor \u2014 scan the agent configuration files of this project and show the findings.",
20453
+ "",
20454
+ 'The files are listed first and uploaded only after a yes; they are deleted within one hour at most and the report is private. Needs a signed-in device ("arnesa login"); an API key does not open Doctor.',
20455
+ "",
20456
+ "Usage: arnesa doctor [--yes] [--json]",
20457
+ "",
20458
+ "Options:",
20459
+ " --yes Upload the listed files without asking",
20460
+ " --json Print the result as one JSON document",
20461
+ "",
20462
+ "Exit codes:",
20463
+ " 0 the scan finished (or no agent configuration files were found)",
20464
+ " 1 not signed in as a device, or the scan failed",
20465
+ " 3 confirmation is required and there is no terminal",
20466
+ " 5 rate limited"
20467
+ ],
20468
+ login: [
20469
+ "login \u2014 sign in to Arnesa, or store an API key.",
20470
+ "",
20471
+ "Without options, sign in with your browser through a device code; the token is stored in the OS keychain. With --key, read an API key (ak_) from standard input and store it instead, for scripts and machines without a browser.",
20472
+ "",
20473
+ "Usage: arnesa login [--key] [--json]",
20474
+ "",
20475
+ "Options:",
20476
+ " --key Read the key from standard input and store it in the keychain",
20477
+ " --json Print the result as one JSON document",
20478
+ "",
20479
+ "Exit codes:",
20480
+ " 0 signed in, or the key was stored",
20481
+ " 1 no OS keychain is available, the sign-in expired, or the credential could not be stored",
20482
+ " 2 the pasted key is not a valid ak_ key, or no API URL is set",
20483
+ " 4 the sign-in was denied",
20484
+ " 5 rate limited"
20485
+ ],
20486
+ logout: [
20487
+ "logout \u2014 sign out: revoke the device token on the server and remove the stored credential from this machine's keychain.",
20488
+ "",
20489
+ 'With a device token, one revoke call is made (5 second timeout, never retried) and the keychain entry is always removed afterwards: a confirmed revoke answers "Signed out and revoked this device.", an already-revoked device says so, and a server or network failure still clears the local entry and asks you to revoke the device at your /me page. With an Arnesa key (ak_), no revoke call is made: the key stays valid until you revoke it at your /me page. The ARNESA_API_KEY environment variable is never read or changed.',
20490
+ "",
20491
+ "Usage: arnesa logout [--json]",
20492
+ "",
20493
+ "Options:",
20494
+ " --json Print the result as one JSON document",
20495
+ "",
20496
+ "Exit codes:",
20497
+ " 0 done: the device was revoked, was already revoked, or the stored credential was removed even when the server could not be reached (nothing stored is also fine)"
20498
+ ],
20499
+ config: [
20500
+ "config \u2014 show or change settings for your account.",
20501
+ "",
20502
+ 'Needs a signed-in device ("arnesa login"); an environment API key is deliberately not used here.',
20503
+ "",
20504
+ "Usage: arnesa config telemetry [on|off]",
20505
+ "",
20506
+ 'Without a value the current state is shown. "on" and "off" change metadata sharing: what an install reports (the harness, the model, the runtime, the self-reported result, an error code \u2014 never source code, prompts or file paths).',
20507
+ "",
20508
+ "Exit codes:",
20509
+ " 0 done",
20510
+ " 1 not signed in as a device",
20511
+ " 2 wrong usage (the only topic is telemetry)"
20512
+ ]
20513
+ };
20372
20514
  function pct(x) {
20373
20515
  return `${Math.round(x * 100)}%`;
20374
20516
  }
@@ -20449,7 +20591,7 @@ function createHttpClient(options) {
20449
20591
  );
20450
20592
  try {
20451
20593
  const headers = { accept: "application/json", origin };
20452
- if (o.body !== void 0) headers["content-type"] = "application/json";
20594
+ if (method !== "GET") headers["content-type"] = "application/json";
20453
20595
  if (o.token) headers.authorization = `Bearer ${o.token}`;
20454
20596
  let res;
20455
20597
  try {
@@ -20506,7 +20648,7 @@ function createHttpClient(options) {
20506
20648
  const { code: code2, message, details } = err.data.error;
20507
20649
  throw new ApiError(message, exitForApiError(code2, res.status), code2, details ?? null);
20508
20650
  }
20509
- const ok = OkEnvelope.safeParse(json2);
20651
+ const ok = res.status === 204 ? { success: true, data: { data: null } } : OkEnvelope.safeParse(json2);
20510
20652
  const data = ok.success ? o.schema.safeParse(ok.data.data) : null;
20511
20653
  if (!data?.success) throw new ApiError(messages.badResponse, EXIT.failure, "BAD_RESPONSE");
20512
20654
  return data.data;
@@ -21708,9 +21850,51 @@ async function runLogin(args, ctx) {
21708
21850
  return EXIT.ok;
21709
21851
  }
21710
21852
  }
21853
+ var REVOKE_TIMEOUT_MS = 5e3;
21854
+ function logoutReasonOf(error62) {
21855
+ if (error62.code === "NETWORK" || error62.code === "TIMEOUT") return "network";
21856
+ const details = external_exports.object({ reason: external_exports.string().optional() }).safeParse(error62.details);
21857
+ if (error62.code === "UNAUTHENTICATED" && details.success && details.data.reason === "CREDENTIAL_REVOKED") {
21858
+ return "already_revoked";
21859
+ }
21860
+ return "server_error";
21861
+ }
21711
21862
  async function runLogout(_args, ctx) {
21712
- (await ctx.keychain()).delete();
21713
- ctx.emit({ loggedOut: true }, [messages.loggedOut]);
21863
+ const keychain = await ctx.keychain();
21864
+ const stored = keychain.available ? keychain.get() : null;
21865
+ if (!stored) {
21866
+ keychain.delete();
21867
+ ctx.emit({ loggedOut: true, revoked: false, reason: "nothing_stored" }, [
21868
+ messages.logoutNothingStored
21869
+ ]);
21870
+ return EXIT.ok;
21871
+ }
21872
+ const lines = [];
21873
+ let revoked = false;
21874
+ let reason;
21875
+ if (stored.startsWith("ad_")) {
21876
+ try {
21877
+ await ctx.client().request("POST", "/api/v1/device/revoke", {
21878
+ token: stored,
21879
+ timeoutMs: REVOKE_TIMEOUT_MS,
21880
+ schema: external_exports.null()
21881
+ });
21882
+ revoked = true;
21883
+ reason = "revoked";
21884
+ lines.push(messages.logoutRevoked);
21885
+ } catch (error62) {
21886
+ reason = error62 instanceof ApiError ? logoutReasonOf(error62) : "server_error";
21887
+ lines.push(
21888
+ reason === "already_revoked" ? messages.logoutAlreadyRevoked : messages.logoutRevokeFailed
21889
+ );
21890
+ }
21891
+ } else {
21892
+ reason = "key_not_revocable";
21893
+ lines.push(messages.logoutKeyOnly);
21894
+ }
21895
+ keychain.delete();
21896
+ if (ctx.io.env.ARNESA_API_KEY?.trim()) lines.push(messages.logoutEnvKeyUntouched);
21897
+ ctx.emit({ loggedOut: true, revoked, reason }, lines);
21714
21898
  return EXIT.ok;
21715
21899
  }
21716
21900
  async function runConfig(_args, ctx) {
@@ -22169,6 +22353,7 @@ var COMMANDS = {
22169
22353
  logout: runLogout,
22170
22354
  config: runConfig
22171
22355
  };
22356
+ var COMMAND_NAMES = Object.keys(COMMANDS);
22172
22357
  var OPTIONS = {
22173
22358
  json: { type: "boolean" },
22174
22359
  yes: { type: "boolean", short: "y" },
@@ -22210,9 +22395,13 @@ async function main(argv, terminal) {
22210
22395
  return EXIT.ok;
22211
22396
  }
22212
22397
  const name = positionals[0];
22213
- if (values.help || !name) {
22214
- for (const line of messages.usage) (values.help ? io.out : io.err)(line);
22215
- if (values.help) return EXIT.ok;
22398
+ if (values.help) {
22399
+ if (name && !COMMANDS[name]) return fail(messages.unknownCommand(name), EXIT.usage, "USAGE");
22400
+ for (const line of (name ? commandHelp[name] : void 0) ?? messages.usage) io.out(line);
22401
+ return EXIT.ok;
22402
+ }
22403
+ if (!name) {
22404
+ for (const line of messages.usage) io.err(line);
22216
22405
  return fail(messages.noCommand, EXIT.usage, "USAGE");
22217
22406
  }
22218
22407
  const command = COMMANDS[name];
@@ -22298,6 +22487,6 @@ var code = await main(process.argv.slice(2), {
22298
22487
  isTty: Boolean(process.stdin.isTTY && process.stderr.isTTY),
22299
22488
  openKeychain: systemKeychain,
22300
22489
  runtimeVersion: installedVersion,
22301
- version: true ? "0.1.0" : "0.0.0-dev"
22490
+ version: true ? "0.1.1" : "0.0.0-dev"
22302
22491
  });
22303
22492
  process.exitCode = code;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arnesa",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Find, install and check coding-agent harnesses from your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",