tines 0.0.149 → 0.0.151

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 +100 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3775,6 +3775,46 @@ function requirementFix(r, ref) {
3775
3775
  };
3776
3776
  }
3777
3777
 
3778
+ // ../shared/src/artifact-site.ts
3779
+ var ARTIFACT_SITE_LINK_TTL_MS = 60 * 60 * 1e3;
3780
+ var ARTIFACT_SITE_INDEX = "index.html";
3781
+ function siteEntry(type, contentType, files = []) {
3782
+ if (type === "file" || type === "text") {
3783
+ return (contentType ?? "").toLowerCase().startsWith("text/html") ? "" : null;
3784
+ }
3785
+ if (type === "folder") {
3786
+ return files.some((f) => f.path === ARTIFACT_SITE_INDEX) ? ARTIFACT_SITE_INDEX : null;
3787
+ }
3788
+ return null;
3789
+ }
3790
+ var VIEWPORT_META = /<meta[^>]+name\s*=\s*["']?viewport["']?[^>]*content\s*=\s*["'][^"']*width\s*=\s*device-width/i;
3791
+ var EXTERNAL_URL = /^(?:https?:)?\/\//i;
3792
+ function externalRefs(html) {
3793
+ const found = [];
3794
+ const push = (url) => {
3795
+ if (url && EXTERNAL_URL.test(url.trim()) && !found.includes(url.trim())) found.push(url.trim());
3796
+ };
3797
+ for (const m of html.matchAll(/<script[^>]*\ssrc\s*=\s*["']([^"']+)["']/gi)) push(m[1]);
3798
+ for (const m of html.matchAll(/<link[^>]*\shref\s*=\s*["']([^"']+)["']/gi)) push(m[1]);
3799
+ for (const m of html.matchAll(/@import\s+(?:url\()?\s*["']([^"']+)["']/gi)) push(m[1]);
3800
+ return found;
3801
+ }
3802
+ function lintHtmlArtifact(html) {
3803
+ const warnings = [];
3804
+ if (!VIEWPORT_META.test(html)) {
3805
+ warnings.push(
3806
+ 'no viewport meta tag \u2014 add <meta name="viewport" content="width=device-width, initial-scale=1"> so it lays out for phones'
3807
+ );
3808
+ }
3809
+ const external = externalRefs(html);
3810
+ if (external.length > 0) {
3811
+ warnings.push(
3812
+ `external scripts/styles are blocked by the sandbox policy (${external.slice(0, 3).join(", ")}${external.length > 3 ? ", \u2026" : ""}) \u2014 inline or vendor them into the artifact`
3813
+ );
3814
+ }
3815
+ return warnings;
3816
+ }
3817
+
3778
3818
  // ../shared/src/client.ts
3779
3819
  var ApiError = class extends Error {
3780
3820
  status;
@@ -3957,6 +3997,11 @@ function createApiClient(options) {
3957
3997
  // Issue artifacts (name-addressed under the issue)
3958
3998
  listArtifacts: (issueId) => get(`/api/v1/issues/${issueId}/artifacts`),
3959
3999
  getArtifact: (issueId, name2) => get(artifactPath(issueId, name2)),
4000
+ /**
4001
+ * Mints a short-lived signed URL that renders an HTML artifact live
4002
+ * (422 `not_a_site` when the artifact is not HTML / has no index.html).
4003
+ */
4004
+ createArtifactSiteLink: (issueId, name2, body = {}) => request("POST", artifactPath(issueId, name2, "/site-link"), body),
3960
4005
  /** JSON upsert for text/link/pr: creates the artifact or appends a version. */
3961
4006
  putArtifact: (issueId, name2, body) => request("PUT", artifactPath(issueId, name2), body),
3962
4007
  /** Raw-body upload for `file`: creates the artifact or appends a version. */
@@ -5887,6 +5932,16 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
5887
5932
  `current: v${artifact.current_version.version} (${artifact.fresh ? "fresh" : "attached before the current state \u2014 reaffirm or attach a new version to satisfy gates"})`
5888
5933
  );
5889
5934
  console.log(`summary: ${artifactSummary(artifact)}`);
5935
+ const entry = siteEntry(
5936
+ artifact.artifact_type,
5937
+ artifact.current_version.content_type,
5938
+ artifact.current_version.files ?? []
5939
+ );
5940
+ if (entry !== null) {
5941
+ console.log(
5942
+ `site: renders live from /${entry} \u2014 "tines issues artifacts site-link ${issue.project_name}/${issue.number} ${artifact.name}" for a viewable URL`
5943
+ );
5944
+ }
5890
5945
  console.log("\nversions:");
5891
5946
  table(
5892
5947
  artifact.versions.map((v) => [
@@ -5936,10 +5991,13 @@ files (v${artifact.current_version.version}):`);
5936
5991
  );
5937
5992
  const withDescription = opts.description !== void 0 ? { description: opts.description } : {};
5938
5993
  let artifact;
5994
+ let siteHtml = null;
5939
5995
  if (plan.type === "folder") {
5940
5996
  const dir = plan.source.dir;
5941
5997
  const files = walkFolder(dir);
5942
5998
  if (files.length === 0) die(`${dir} contains no files to snapshot`);
5999
+ const index = files.find((f) => f.path === ARTIFACT_SITE_INDEX);
6000
+ if (index) siteHtml = index.bytes.toString("utf8");
5943
6001
  artifact = await api.uploadArtifactFolder(issue.id, name2, files);
5944
6002
  if (opts.description !== void 0) {
5945
6003
  artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
@@ -5957,15 +6015,18 @@ files (v${artifact.current_version.version}):`);
5957
6015
  die(`cannot read ${fromPath}: ${err instanceof Error ? err.message : String(err)}`);
5958
6016
  }
5959
6017
  }
6018
+ const contentType = plan.contentType ?? (fromPath === null ? "application/octet-stream" : sniffContentType(fromPath));
6019
+ if (siteEntry("file", contentType) !== null) siteHtml = bytes.toString("utf8");
5960
6020
  artifact = await api.uploadArtifactFile(issue.id, name2, bytes, {
5961
6021
  filename: plan.filename ?? (fromPath === null ? name2 : basename(fromPath)),
5962
- contentType: plan.contentType ?? (fromPath === null ? "application/octet-stream" : sniffContentType(fromPath))
6022
+ contentType
5963
6023
  });
5964
6024
  if (opts.description !== void 0) {
5965
6025
  artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
5966
6026
  }
5967
6027
  } else if (plan.type === "text") {
5968
6028
  const content = readTextSource(plan.source);
6029
+ if (siteEntry("text", plan.contentType ?? null) !== null) siteHtml = content;
5969
6030
  artifact = await api.putArtifact(issue.id, name2, {
5970
6031
  type: "text",
5971
6032
  content,
@@ -5996,6 +6057,12 @@ files (v${artifact.current_version.version}):`);
5996
6057
  console.log(
5997
6058
  `attached "${artifact.name}" v${artifact.current_version.version} (${artifactTypeLabel(artifact)}) to ${issue.project_name}/${issue.number} \u2014 fresh${gateNote}`
5998
6059
  );
6060
+ if (siteHtml !== null) {
6061
+ console.log(
6062
+ `site: renders live \u2014 "tines issues artifacts site-link ${issue.project_name}/${issue.number} ${artifact.name}" for a viewable URL`
6063
+ );
6064
+ for (const warning of lintHtmlArtifact(siteHtml)) console.log(`warning: ${warning}`);
6065
+ }
5999
6066
  }
6000
6067
  );
6001
6068
  withCommon(
@@ -6011,6 +6078,32 @@ files (v${artifact.current_version.version}):`);
6011
6078
  `reaffirmed "${artifact.name}" on ${issue.project_name}/${issue.number}: v${artifact.current_version.version} reaffirms v${artifact.current_version.reaffirmed_from} \u2014 fresh as of now`
6012
6079
  );
6013
6080
  });
6081
+ withCommon(
6082
+ artifactsCmd.command("site-link <ref> <name>").description(
6083
+ "Mint a short-lived URL that renders an HTML artifact live (scripts running) \u2014 for screenshotting your own prototype"
6084
+ ).option(
6085
+ "--version <n>",
6086
+ "pin the link to a specific version (defaults to current)",
6087
+ (v) => Number.parseInt(v, 10)
6088
+ )
6089
+ ).action(async (ref, name2, opts) => {
6090
+ const api = client(opts);
6091
+ const issue = await resolveIssue(api, ref);
6092
+ const link = await api.createArtifactSiteLink(
6093
+ issue.id,
6094
+ name2,
6095
+ opts.version === void 0 ? {} : { version: opts.version }
6096
+ );
6097
+ if (opts.json) return printJson(link);
6098
+ console.log(link.url);
6099
+ const minutes = Math.max(1, Math.round((link.expires_at - Date.now()) / 6e4));
6100
+ console.log(`v${link.version}, expires in ~${minutes} minute${minutes === 1 ? "" : "s"}`);
6101
+ if (link.mode === "same-origin") {
6102
+ console.log(
6103
+ "mode: same-origin sandbox (opaque origin) \u2014 localStorage and cookies throw inside the page"
6104
+ );
6105
+ }
6106
+ });
6014
6107
  withCommon(
6015
6108
  artifactsCmd.command("get <ref> <name>").description("Fetch content (current version by default); a link/pr prints its URL").option(
6016
6109
  "--version <n>",
@@ -6990,7 +7083,12 @@ function buildHarnessInvocation(spec, input) {
6990
7083
  case "codex":
6991
7084
  return {
6992
7085
  file: "codex",
6993
- args: ["exec", ...input.model ? ["--model", input.model] : [], input.prompt]
7086
+ args: [
7087
+ "exec",
7088
+ "--skip-git-repo-check",
7089
+ ...input.model ? ["--model", input.model] : [],
7090
+ input.prompt
7091
+ ]
6994
7092
  };
6995
7093
  case "custom": {
6996
7094
  if (!spec.command) throw new Error("the custom harness needs a --command template");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.149",
3
+ "version": "0.0.151",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",