tines 0.0.90 → 0.0.92

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/dist/index.js +108 -57
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/program.ts
4
- import { readFileSync as readFileSync8 } from "node:fs";
5
-
6
3
  // ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
7
4
  var CommanderError = class extends Error {
8
5
  /**
@@ -4186,15 +4183,12 @@ function parseTargetSpec(spec) {
4186
4183
  }
4187
4184
 
4188
4185
  // src/common.ts
4189
- var DEFAULT_URL = "http://localhost:5173";
4190
- function withCommon(cmd, { baseUrlFlag = true } = {}) {
4191
- if (baseUrlFlag) {
4192
- cmd.option(
4193
- "-u, --url <url>",
4194
- `base URL of the Tines API (or set TINES_API_URL; default ${DEFAULT_URL})`
4195
- );
4196
- }
4197
- return cmd.option("--api-key <key>", "API key (or set TINES_API_KEY)").option("--json", "output the raw JSON response");
4186
+ var DEFAULT_URL = "https://tines.tbuckley.dev";
4187
+ function withCommon(cmd) {
4188
+ return cmd.option(
4189
+ "-u, --url <url>",
4190
+ `base URL of the Tines API (or set TINES_API_URL; default ${DEFAULT_URL})`
4191
+ ).option("--api-key <key>", "API key (or set TINES_API_KEY)").option("--json", "output the raw JSON response");
4198
4192
  }
4199
4193
  function withList(cmd) {
4200
4194
  return withCommon(
@@ -4420,13 +4414,14 @@ function register(program3) {
4420
4414
  );
4421
4415
  withCommon(
4422
4416
  withScopeFlags(
4423
- context.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, or repo").requiredOption("-n, --name <name>", "item name (slug-like for skills; the dedup/override key)").option("-d, --description <text>", "one-liner shown in lists").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "skill file: workspace path = local file (repeatable)", collect, []).option("--url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch to check out").option("--dir <dir>", "repo: checkout directory (defaults to the URL's basename)")
4424
- ),
4425
- // --url is the repo pointer here; the API base comes from TINES_API_URL.
4426
- { baseUrlFlag: false }
4417
+ context.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, or repo").requiredOption("-n, --name <name>", "item name (slug-like for skills; the dedup/override key)").option("-d, --description <text>", "one-liner shown in lists").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "skill file: workspace path = local file (repeatable)", collect, []).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch to check out").option("--dir <dir>", "repo: checkout directory (defaults to the URL's basename)")
4418
+ )
4427
4419
  ).action(
4428
4420
  async (opts) => {
4429
- const api = client({ apiKey: opts.apiKey, json: opts.json });
4421
+ if (opts.kind === "repo" && opts.repoUrl === void 0) {
4422
+ die("--kind repo needs --repo-url <clone-url> (--url is the API base URL)");
4423
+ }
4424
+ const api = client(opts);
4430
4425
  const scope = await resolveScopeFlags(api, opts);
4431
4426
  const body = {
4432
4427
  kind: opts.kind,
@@ -4437,7 +4432,7 @@ function register(program3) {
4437
4432
  if (opts.body !== void 0) body.body = readBodyValue(opts.body);
4438
4433
  if (opts.file.length > 0) body.files = opts.file.map(parseFileSpec);
4439
4434
  if (opts.kind === "skill" && body.files === void 0) body.files = [];
4440
- if (opts.url !== void 0) body.repo_url = opts.url;
4435
+ if (opts.repoUrl !== void 0) body.repo_url = opts.repoUrl;
4441
4436
  if (opts.branch !== void 0) body.repo_branch = opts.branch;
4442
4437
  if (opts.dir !== void 0) body.repo_dir = opts.dir;
4443
4438
  const item = await api.createContextItem(body);
@@ -4447,17 +4442,15 @@ function register(program3) {
4447
4442
  );
4448
4443
  withCommon(
4449
4444
  withScopeFlags(
4450
- context.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option("--unset <dimension>", "drop a scope dimension: project, state, or issue (repeatable)", collect, []).option(
4445
+ context.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option("--unset <dimension>", "drop a scope dimension: project, state, or issue (repeatable)", collect, []).option(
4451
4446
  "--expect-version <n>",
4452
4447
  "fail (409) unless the item is still at this version",
4453
4448
  (v) => Number.parseInt(v, 10)
4454
4449
  )
4455
- ),
4456
- // --url is the repo pointer here; the API base comes from TINES_API_URL.
4457
- { baseUrlFlag: false }
4450
+ )
4458
4451
  ).action(
4459
4452
  async (id, opts) => {
4460
- const api = client({ apiKey: opts.apiKey, json: opts.json });
4453
+ const api = client(opts);
4461
4454
  const body = {};
4462
4455
  if (opts.expectVersion !== void 0) body.expected_version = opts.expectVersion;
4463
4456
  if (opts.name !== void 0) body.name = opts.name;
@@ -4487,7 +4480,7 @@ function register(program3) {
4487
4480
  }
4488
4481
  body.files = [...files.entries()].map(([path2, content]) => ({ path: path2, content }));
4489
4482
  }
4490
- if (opts.url !== void 0) body.repo_url = opts.url;
4483
+ if (opts.repoUrl !== void 0) body.repo_url = opts.repoUrl;
4491
4484
  if (opts.branch !== void 0) body.repo_branch = opts.branch === "" ? null : opts.branch;
4492
4485
  if (opts.dir !== void 0) body.repo_dir = opts.dir === "" ? null : opts.dir;
4493
4486
  if (Object.keys(body).length === 0) {
@@ -5028,16 +5021,14 @@ files (v${artifact.current_version.version}):`);
5028
5021
  artifactsCmd.command("attach <ref> <name>").description("Attach content to a named artifact slot (creates it, or appends the next version)").option("-f, --file <path>", "upload a file (MIME sniffed from the extension)").option(
5029
5022
  "--folder <dir>",
5030
5023
  "snapshot a directory tree as one version (collect locally, attach once; MIME per file sniffed)"
5031
- ).option("-t, --text <md|@file>", "inline text document: inline Markdown or @file").option("--url <url>", "link: the URL to attach").option("--pr <spec>", "PR reference: owner/repo#N or a GitHub PR URL").option("--content-type <mime>", "declared MIME type (with --file or --text)").option("--filename <name>", "display filename (with --text; defaults to <name>.md)").option("--title <title>", "display title (with --url)").option("-d, --description <text>", "artifact description, shown in lists and launch prompts"),
5032
- // --url is the link payload here; the API base comes from TINES_API_URL.
5033
- { baseUrlFlag: false }
5024
+ ).option("-t, --text <md|@file>", "inline text document: inline Markdown or @file").option("--link <url>", "link: the URL to attach").option("--pr <spec>", "PR reference: owner/repo#N or a GitHub PR URL").option("--content-type <mime>", "declared MIME type (with --file or --text)").option("--filename <name>", "display filename (with --text; defaults to <name>.md)").option("--title <title>", "display title (with --link)").option("-d, --description <text>", "artifact description, shown in lists and launch prompts")
5034
5025
  ).action(
5035
5026
  async (ref, name2, opts) => {
5036
- const api = client({ apiKey: opts.apiKey, json: opts.json });
5037
- const sources = [opts.file, opts.folder, opts.text, opts.url, opts.pr].filter((v) => v !== void 0);
5027
+ const api = client(opts);
5028
+ const sources = [opts.file, opts.folder, opts.text, opts.link, opts.pr].filter((v) => v !== void 0);
5038
5029
  if (sources.length !== 1) {
5039
5030
  die(
5040
- "pass exactly one content source: --file <path>, --folder <dir>, --text <md|@file>, --url <url>, or --pr <spec>"
5031
+ "pass exactly one content source: --file <path>, --folder <dir>, --text <md|@file>, --link <url>, or --pr <spec> (a link goes in --link; --url is the API base URL)"
5041
5032
  );
5042
5033
  }
5043
5034
  const issue = await resolveIssue(api, ref);
@@ -5074,10 +5065,10 @@ files (v${artifact.current_version.version}):`);
5074
5065
  ...opts.contentType !== void 0 ? { content_type: opts.contentType } : {},
5075
5066
  ...opts.description !== void 0 ? { description: opts.description } : {}
5076
5067
  });
5077
- } else if (opts.url !== void 0) {
5068
+ } else if (opts.link !== void 0) {
5078
5069
  artifact = await api.putArtifact(issue.id, name2, {
5079
5070
  type: "link",
5080
- url: opts.url,
5071
+ url: opts.link,
5081
5072
  ...opts.title !== void 0 ? { title: opts.title } : {},
5082
5073
  ...opts.description !== void 0 ? { description: opts.description } : {}
5083
5074
  });
@@ -5533,7 +5524,7 @@ import {
5533
5524
  createWriteStream,
5534
5525
  existsSync as existsSync4,
5535
5526
  mkdirSync as mkdirSync4,
5536
- readFileSync as readFileSync6,
5527
+ readFileSync as readFileSync7,
5537
5528
  rmSync as rmSync2,
5538
5529
  statSync as statSync3,
5539
5530
  unlinkSync,
@@ -5542,6 +5533,17 @@ import {
5542
5533
  import { hostname, platform, arch } from "node:os";
5543
5534
  import { dirname as dirname4, join as join4 } from "node:path";
5544
5535
 
5536
+ // src/version.ts
5537
+ import { readFileSync as readFileSync4 } from "node:fs";
5538
+ function cliVersion() {
5539
+ try {
5540
+ const manifest = new URL("../package.json", import.meta.url);
5541
+ return JSON.parse(readFileSync4(manifest, "utf8")).version ?? "0.0.0-unknown";
5542
+ } catch {
5543
+ return "0.0.0-unknown";
5544
+ }
5545
+ }
5546
+
5545
5547
  // src/daemon/claude-stream.ts
5546
5548
  function clip(value, max) {
5547
5549
  return value.length > max ? `${value.slice(0, max)}\u2026` : value;
@@ -5639,7 +5641,7 @@ var ClaudeStreamRenderer = class {
5639
5641
 
5640
5642
  // src/daemon/cli-refresh.ts
5641
5643
  import { spawn } from "node:child_process";
5642
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "node:fs";
5644
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
5643
5645
  import { dirname as dirname2, join as join2 } from "node:path";
5644
5646
 
5645
5647
  // src/daemon/support.ts
@@ -5676,6 +5678,47 @@ function buildHarnessInvocation(spec, input) {
5676
5678
  }
5677
5679
  }
5678
5680
  }
5681
+ var LAUNCH_ARG_MAX = 160;
5682
+ var SAFE_WORD = /^[A-Za-z0-9_@%+=:,./-]+$/;
5683
+ function launchWord(word) {
5684
+ if (word.length > LAUNCH_ARG_MAX)
5685
+ return shellQuote(`${word.slice(0, LAUNCH_ARG_MAX)}\u2026 [+${word.length - LAUNCH_ARG_MAX} chars]`);
5686
+ return SAFE_WORD.test(word) ? word : shellQuote(word);
5687
+ }
5688
+ function formatLaunchCommand(invocation) {
5689
+ if (invocation.file === "sh" && invocation.args.length === 2 && invocation.args[0] === "-c")
5690
+ return invocation.args[1];
5691
+ return [invocation.file, ...invocation.args].map(launchWord).join(" ");
5692
+ }
5693
+ function formatLaunchBanner(invocation, input, meta) {
5694
+ const fields = [
5695
+ `harness=${meta.harness}`,
5696
+ `model=${input.model ?? "(fixed)"}`,
5697
+ `timeout=${meta.timeoutMinutes}m`,
5698
+ `cli=${meta.cliVersion}`,
5699
+ `workspace=${input.workspace}`
5700
+ ];
5701
+ return `$ ${formatLaunchCommand(invocation)}
5702
+ # tines runner: ${fields.join(" ")}
5703
+ `;
5704
+ }
5705
+ function formatDuration(ms) {
5706
+ const seconds = Math.max(0, Math.round(ms / 1e3));
5707
+ return `${Math.floor(seconds / 60)}m${seconds % 60}s`;
5708
+ }
5709
+ function formatExitLine(exit) {
5710
+ const parts = [];
5711
+ if (exit.code !== null) parts.push(`code=${exit.code}`);
5712
+ if (exit.signal) parts.push(`signal=${exit.signal}`);
5713
+ if (parts.length === 0) parts.push("code=?");
5714
+ if (exit.timedOut) parts.push("(timed out)");
5715
+ return `# tines runner: exit ${parts.join(" ")} after ${formatDuration(exit.durationMs)}
5716
+ `;
5717
+ }
5718
+ function exitLineForRun(run, exit) {
5719
+ if (run.settled) return null;
5720
+ return formatExitLine({ ...exit, timedOut: run.timedOut });
5721
+ }
5679
5722
  var RunTable = class {
5680
5723
  constructor(effects, opts = {}) {
5681
5724
  this.effects = effects;
@@ -5881,7 +5924,7 @@ function binDirOf(prefix) {
5881
5924
  }
5882
5925
  function installedVersion(prefix) {
5883
5926
  try {
5884
- const pkg = readFileSync4(join2(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
5927
+ const pkg = readFileSync5(join2(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
5885
5928
  const version = JSON.parse(pkg).version;
5886
5929
  return typeof version === "string" ? version : null;
5887
5930
  } catch {
@@ -5962,7 +6005,7 @@ import {
5962
6005
  existsSync as existsSync3,
5963
6006
  mkdirSync as mkdirSync3,
5964
6007
  readdirSync as readdirSync2,
5965
- readFileSync as readFileSync5,
6008
+ readFileSync as readFileSync6,
5966
6009
  rmSync,
5967
6010
  statSync as statSync2,
5968
6011
  writeFileSync as writeFileSync2
@@ -5975,7 +6018,7 @@ function defaultConfigDir() {
5975
6018
  function readJsonFile(path2) {
5976
6019
  if (!existsSync3(path2)) return null;
5977
6020
  try {
5978
- return JSON.parse(readFileSync5(path2, "utf8"));
6021
+ return JSON.parse(readFileSync6(path2, "utf8"));
5979
6022
  } catch {
5980
6023
  return null;
5981
6024
  }
@@ -6026,10 +6069,10 @@ function saveDaemonState(path2, runs) {
6026
6069
  }
6027
6070
  function processStartTimeMs(pid) {
6028
6071
  try {
6029
- const stat = readFileSync5(`/proc/${pid}/stat`, "utf8");
6072
+ const stat = readFileSync6(`/proc/${pid}/stat`, "utf8");
6030
6073
  const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
6031
6074
  const startTicks = Number(afterComm[19]);
6032
- const btimeLine = readFileSync5("/proc/stat", "utf8").split("\n").find((line) => line.startsWith("btime "));
6075
+ const btimeLine = readFileSync6("/proc/stat", "utf8").split("\n").find((line) => line.startsWith("btime "));
6033
6076
  const btime = Number(btimeLine?.slice("btime ".length));
6034
6077
  if (!Number.isFinite(startTicks) || !Number.isFinite(btime)) return null;
6035
6078
  return btime * 1e3 + startTicks / 100 * 1e3;
@@ -6114,6 +6157,7 @@ function directorySizeBytes(path2) {
6114
6157
 
6115
6158
  // src/daemon/daemon.ts
6116
6159
  var CLI_REFRESH_TTL_MS = 10 * 6e4;
6160
+ var DAEMON_VERSION = cliVersion();
6117
6161
  async function uploadRawLog(run) {
6118
6162
  const path2 = run.rawSpoolPath;
6119
6163
  if (!path2 || !run.rawUpload) return;
@@ -6122,7 +6166,7 @@ async function uploadRawLog(run) {
6122
6166
  await new Promise((resolve) => run.rawSpool?.end(resolve) ?? resolve());
6123
6167
  const size = statSync3(path2).size;
6124
6168
  if (size > 0) {
6125
- let body = readFileSync6(path2);
6169
+ let body = readFileSync7(path2);
6126
6170
  if (body.byteLength > RUN_LOG_RAW_MAX_BYTES) {
6127
6171
  const marker = Buffer.from(
6128
6172
  `{"type":"tines_truncated","dropped_bytes":${body.byteLength - RUN_LOG_RAW_MAX_BYTES}}
@@ -6347,14 +6391,22 @@ async function runDaemon(opts) {
6347
6391
  ` : `warning: no daemon-managed tines CLI; using whatever \`tines\` is on this machine's PATH
6348
6392
  `
6349
6393
  );
6394
+ const harnessInput = {
6395
+ workspace,
6396
+ promptFile: join4(workspace, "prompt.md"),
6397
+ prompt: assignment.prompt,
6398
+ model: assignment.run.model
6399
+ };
6350
6400
  const invocation = buildHarnessInvocation(
6351
6401
  { harness: opts.harness, command: opts.command },
6352
- {
6353
- workspace,
6354
- promptFile: join4(workspace, "prompt.md"),
6355
- prompt: assignment.prompt,
6356
- model: assignment.run.model
6357
- }
6402
+ harnessInput
6403
+ );
6404
+ run.batcher.append(
6405
+ formatLaunchBanner(invocation, harnessInput, {
6406
+ harness: opts.harness,
6407
+ timeoutMinutes: assignment.timeout_minutes,
6408
+ cliVersion: DAEMON_VERSION
6409
+ })
6358
6410
  );
6359
6411
  const child = spawn2(invocation.file, invocation.args, {
6360
6412
  cwd: workspace,
@@ -6401,6 +6453,13 @@ async function runDaemon(opts) {
6401
6453
  void table2.finishAndCleanup(run, "failed", `failed to launch harness: ${message2(err)}`);
6402
6454
  });
6403
6455
  child.on("exit", (code, signal) => {
6456
+ run.drain?.();
6457
+ const closing = exitLineForRun(run, {
6458
+ code,
6459
+ signal,
6460
+ durationMs: Date.now() - (run.spawnedAt ?? Date.now())
6461
+ });
6462
+ if (closing) run.batcher.append(closing);
6404
6463
  if (run.timedOut) {
6405
6464
  void table2.finishAndCleanup(
6406
6465
  run,
@@ -7138,7 +7197,7 @@ function register8(program3) {
7138
7197
  }
7139
7198
 
7140
7199
  // src/commands/workflows.ts
7141
- import { readFileSync as readFileSync7 } from "node:fs";
7200
+ import { readFileSync as readFileSync8 } from "node:fs";
7142
7201
  function readJsonBody(inline, file) {
7143
7202
  if (inline !== void 0 && file !== void 0) {
7144
7203
  die("pass the JSON inline or with --file, not both");
@@ -7147,14 +7206,14 @@ function readJsonBody(inline, file) {
7147
7206
  if (file !== void 0 && file !== "-") {
7148
7207
  let raw;
7149
7208
  try {
7150
- raw = readFileSync7(file, "utf8");
7209
+ raw = readFileSync8(file, "utf8");
7151
7210
  } catch (err) {
7152
7211
  die(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
7153
7212
  }
7154
7213
  return parseJsonObject(raw, file);
7155
7214
  }
7156
7215
  if (file === "-" || !process.stdin.isTTY) {
7157
- const raw = readFileSync7(0, "utf8");
7216
+ const raw = readFileSync8(0, "utf8");
7158
7217
  if (raw.trim() === "") {
7159
7218
  if (file === "-") die("no JSON on stdin");
7160
7219
  return void 0;
@@ -7308,14 +7367,6 @@ see \`tines workflows create --help\` for the expected shape`
7308
7367
  }
7309
7368
 
7310
7369
  // src/program.ts
7311
- function cliVersion() {
7312
- try {
7313
- const manifest = new URL("../package.json", import.meta.url);
7314
- return JSON.parse(readFileSync8(manifest, "utf8")).version ?? "0.0.0-unknown";
7315
- } catch {
7316
- return "0.0.0-unknown";
7317
- }
7318
- }
7319
7370
  var program2 = new Command();
7320
7371
  program2.name("tines").description("CLI for Tines").version(cliVersion()).enablePositionalOptions();
7321
7372
  registerTime(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.90",
3
+ "version": "0.0.92",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "scripts": {
28
28
  "build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=esm --banner:js=\"#!/usr/bin/env node\" --outfile=dist/index.js",
29
- "start": "tsx src/index.ts",
29
+ "start": "TINES_API_URL=${TINES_API_URL:-http://localhost:5173} tsx src/index.ts",
30
30
  "check": "tsc --noEmit",
31
31
  "test": "vitest run"
32
32
  }