@hitch42/applet 0.1.1-beta.19 → 0.1.1-beta.21

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 (96) hide show
  1. package/dist/_internal/client.d.ts +0 -2
  2. package/dist/_internal/client.js +3 -5
  3. package/dist/_internal/log-context.d.ts +0 -2
  4. package/dist/_internal/log-context.js +1 -2
  5. package/dist/_internal/server.d.ts +2 -10
  6. package/dist/_internal/server.js +9 -14
  7. package/dist/applet.js +64 -23
  8. package/dist/auth-DuMRqSGe.js +49 -0
  9. package/dist/{batch-_ZjAg-Kk.js → batch-CiLZ177G.js} +3 -4
  10. package/dist/build-C7hi2yg4.js +1 -0
  11. package/dist/{build-DkKTiIu7.js → build-FAQSHJgm.js} +4 -3
  12. package/dist/{ci-B5-mH-YW.js → ci-oWlUJKm9.js} +10 -6
  13. package/dist/client/hitch/client.d.ts +1 -1
  14. package/dist/client/hitch/client.js +1 -2
  15. package/dist/client/hitch/phone.d.ts +1 -1
  16. package/dist/client/hitch/phone.js +1 -2
  17. package/dist/client/hitch.d.ts +2 -2
  18. package/dist/client/hitch.js +2 -2
  19. package/dist/client/react/markdown-editor.d.ts +1 -1
  20. package/dist/client/react/markdown-editor.js +1 -2
  21. package/dist/client/react/markdown.d.ts +1 -1
  22. package/dist/client/react/markdown.js +1 -2
  23. package/dist/client/react.d.ts +2 -2
  24. package/dist/client/react.js +2 -2
  25. package/dist/client.d.ts +2 -4
  26. package/dist/client.js +3 -5
  27. package/dist/{contract-CJv-n1l3.d.ts → contract-CUc0B2rl.d.ts} +1 -3
  28. package/dist/{contract-BeKz1Qcs.js → contract-D9LCNGjD.js} +1 -2
  29. package/dist/create-BJqLT8Uq.d.ts +68 -0
  30. package/dist/{deploy-DODTE8ri.js → deploy-BM55ZoSp.js} +160 -152
  31. package/dist/{device-BgIfytaR.js → device-CZWWckCq.js} +54 -34
  32. package/dist/{email-protocol-B9u_dh2p.d.ts → email-protocol-CEqzASSx.d.ts} +0 -6
  33. package/dist/{generate-8yyxKRSz.js → generate-48pqFh5M.js} +57 -39
  34. package/dist/generate-BYK4aNud.js +1 -0
  35. package/dist/generated-agents-BUnJ0FQA.js +215 -0
  36. package/dist/git-credential-CU7-Erzs.js +24 -0
  37. package/dist/git-deploy-C12nMNB6.js +290 -0
  38. package/dist/git-deploy-CNz6mxiF.js +1 -0
  39. package/dist/git-migration-CIq-sN61.js +629 -0
  40. package/dist/git-safety-Ck3X7jvA.js +13 -0
  41. package/dist/{headers-CeRX-Xji.js → headers-BcEMIVCo.js} +1 -2
  42. package/dist/hitch-client-D33JOEOi.d.ts +22 -0
  43. package/dist/{hitch-client-Cw-WZ_8v.js → hitch-client-Dph_kzDd.js} +5 -7
  44. package/dist/{hitch-file-url-BPKj9zVG.js → hitch-file-url--AMjDOzP.js} +1 -2
  45. package/dist/index.d.ts +8 -15
  46. package/dist/index.js +32 -37
  47. package/dist/{init-DT0faD-q.js → init-Bb6WT8UB.js} +70 -18
  48. package/dist/{lazy-client-CwIzYAoE.js → lazy-client--QCL0wiu.js} +1 -2
  49. package/dist/{load-config-BYrBjPlr.js → load-config-Cffvj-aM.js} +27 -26
  50. package/dist/{log-context-DWznGjfz.js → log-context-BepwdjrR.js} +1 -2
  51. package/dist/login-BWZKo7Ui.js +21 -0
  52. package/dist/{logs-CEIikWzV.js → logs-glCVHI-8.js} +6 -6
  53. package/dist/{manifest-BBHD_EY9.d.ts → manifest-CpghlaJc.d.ts} +6 -7
  54. package/dist/manifest-patterns-BfxqImlp.js +4 -0
  55. package/dist/{markdown-CwlqzBtP.js → markdown-BA_XzFxq.js} +2 -3
  56. package/dist/{markdown-DSHOQIOb.d.ts → markdown-BXt65dDo.d.ts} +1 -3
  57. package/dist/{markdown-editor-BmEaBzd7.d.ts → markdown-editor-CB_nWLzj.d.ts} +1 -5
  58. package/dist/{markdown-editor-C5mLHGqu.js → markdown-editor-CaUuphfc.js} +3 -5
  59. package/dist/parent-D52Kxp8U.js +1 -0
  60. package/dist/parent-Zx8Eg1Ki.js +75 -0
  61. package/dist/{project-CWVwic2C.js → project-DZGk765E.js} +74 -58
  62. package/dist/{prompt-ChS-grYO.js → prompt-CW98R-Un.js} +1 -2
  63. package/dist/{query-cache-CXmAyLv3.js → query-cache-DQJy45RT.js} +1 -2
  64. package/dist/{query-utils-zz9pVjgp.d.ts → query-utils-okxIoKM3.d.ts} +0 -2
  65. package/dist/{schedules-B7xNl9oy.js → schedules-CSUb84e2.js} +5 -5
  66. package/dist/server.d.ts +6 -14
  67. package/dist/server.js +17 -20
  68. package/dist/source-hash-BbvtRaus.js +107 -0
  69. package/dist/store-E5NhITHl.js +448 -0
  70. package/dist/{upload-D_kkmne_.d.ts → upload-DXZyjZSS.d.ts} +6 -10
  71. package/dist/{upload-DRbVhuDm.js → upload-DtMavo8Q.js} +4 -6
  72. package/dist/{use-phone-ug4B5gQp.js → use-phone-CB_idmrz.js} +9 -13
  73. package/dist/{use-phone-M7c_HsB7.d.ts → use-phone-DBAkasPq.d.ts} +0 -4
  74. package/dist/{variables-DCbLJBr0.js → variables-CgZstC7I.js} +7 -8
  75. package/dist/vite.d.ts +1 -3
  76. package/dist/vite.js +42 -51
  77. package/dist/{workflow-DurM6Fwx.d.ts → workflow-BnBrJIzu.d.ts} +0 -2
  78. package/dist/{workflow-engine-BOTzURKM.js → workflow-engine-CyyaVENo.js} +2 -4
  79. package/dist/{workflow-CTo2kZF1.js → workflow-l2SlVePB.js} +2 -4
  80. package/dist/workspace-CtIKSgMD.js +76 -0
  81. package/package.json +6 -5
  82. package/scaffold/.agents/skills/hitch-applets/SKILL.md +28 -10
  83. package/scaffold/.agents/skills/hitch-applets/references/files.md +2 -2
  84. package/scaffold/.agents/skills/hitch-applets/references/manifest.md +8 -6
  85. package/scaffold/.agents/skills/hitch-applets/references/performance.md +2 -2
  86. package/scaffold/.agents/skills/hitch-applets/references/records.md +24 -9
  87. package/scaffold/.agents/skills/hitch-cli/SKILL.md +52 -43
  88. package/scaffold/AGENTS.md +6 -0
  89. package/dist/auth-D3CrCtZq.js +0 -21
  90. package/dist/batch-Qa4B4-Yp.d.ts +0 -30
  91. package/dist/build-DpkWOvd2.js +0 -2
  92. package/dist/generate-Df29-UJm.js +0 -2
  93. package/dist/hitch-client--qZVa2KM.d.ts +0 -23
  94. package/dist/login-CceGvlMi.js +0 -14
  95. package/dist/store-B8N_8JIN.js +0 -196
  96. package/dist/workspace-8yqXsvce.js +0 -31
@@ -1,7 +1,8 @@
1
- import { l as setCredentials, o as rejectEnvironmentKey, t as AuthenticationError } from "./store-B8N_8JIN.js";
1
+ import { c as normalizeInstanceUrl, d as rejectEnvironmentKey, g as saveOAuthCredentials, l as organizationInstanceUrl, o as credentialStorageMessage, t as AuthenticationError, u as readCredentialStore } from "./store-E5NhITHl.js";
2
2
  import { spawn } from "node:child_process";
3
- //#region ../shared/auth/src/device.mts
4
- const CLIENT_ID = "hitch-cli";
3
+ import readline from "node:readline/promises";
4
+ // ../shared/auth/src/device.mts
5
+ const CLIENT_ID = "hitch-cli-oauth";
5
6
  const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
6
7
  function defaultOpenUrl(url) {
7
8
  try {
@@ -40,13 +41,10 @@ function parseDeviceCode(value) {
40
41
  return value;
41
42
  }
42
43
  function parseToken(value) {
43
- return typeof value.access_token === "string" && typeof value.token_type === "string" ? value : void 0;
44
+ return typeof value.access_token === "string" && typeof value.refresh_token === "string" && typeof value.expires_in === "number" && typeof value.token_type === "string" ? value : void 0;
44
45
  }
45
- function parseDeviceOrganization(value) {
46
- if (typeof value.organizationId !== "string" || typeof value.orgCode !== "string") throw new AuthenticationError("Hitch returned an invalid device organization response.");
47
- return value;
48
- }
49
- function parseMe(value) {
46
+ function parseMe(input) {
47
+ const value = requiredObject(input, "Hitch returned an invalid identity response.");
50
48
  const person = requiredObject(value.person, "Hitch returned an invalid identity response.");
51
49
  if (typeof person.id !== "string" || typeof person.firstName !== "string" || typeof person.lastName !== "string" || typeof person.email !== "string" || !Array.isArray(value.memberships)) throw new Error("Hitch returned an invalid identity response.");
52
50
  const organization = value.organization === void 0 || value.organization === null ? null : requiredObject(value.organization, "Hitch returned an invalid identity response.");
@@ -62,24 +60,30 @@ function parseMe(value) {
62
60
  };
63
61
  }
64
62
  async function fetchMe(fetcher, instanceUrl, auth) {
65
- const response = await fetcher(`${instanceUrl}/api/v1/me`, { headers: { Authorization: `Bearer ${auth.accessToken}` } });
63
+ const response = await fetcher(`${instanceUrl}/api/me/v1`, { headers: { Authorization: `Bearer ${auth.accessToken}` } });
66
64
  rejectEnvironmentKey(response, auth);
67
65
  if (auth.credentialType === "apiKey" && response.status === 401) throw new AuthenticationError("API key rejected.");
68
66
  const body = await jsonResponse(response);
69
67
  if (!response.ok) throw new AuthenticationError(typeof body.message === "string" ? body.message : `Failed to load the signed-in identity (${response.status}).`);
70
68
  return parseMe(body);
71
69
  }
72
- async function pollForToken(fetcher, instanceUrl, device, now, sleep) {
70
+ async function pollForToken(fetcher, instanceUrl, resource, device, now, sleep) {
73
71
  const deadline = now() + device.expires_in * 1e3;
74
72
  let interval = (device.interval ?? 5) * 1e3;
75
73
  while (now() < deadline) {
76
74
  await sleep(Math.min(interval, deadline - now()));
77
75
  if (now() >= deadline) break;
78
- const { body, response } = await postJson(fetcher, `${instanceUrl}/api/auth/device/token`, {
79
- grant_type: DEVICE_GRANT,
80
- device_code: device.device_code,
81
- client_id: CLIENT_ID
76
+ const response = await fetcher(`${instanceUrl}/api/auth/oauth2/token`, {
77
+ method: "POST",
78
+ headers: { "content-type": "application/x-www-form-urlencoded" },
79
+ body: new URLSearchParams({
80
+ grant_type: DEVICE_GRANT,
81
+ device_code: device.device_code,
82
+ client_id: CLIENT_ID,
83
+ resource
84
+ })
82
85
  });
86
+ const body = await jsonResponse(response);
83
87
  const token = parseToken(body);
84
88
  if (response.ok && token !== void 0) return token;
85
89
  switch (body.error) {
@@ -97,39 +101,55 @@ async function pollForToken(fetcher, instanceUrl, device, now, sleep) {
97
101
  async function loginWithDevice(instanceUrl, options = {}) {
98
102
  const fetcher = globalThis.fetch;
99
103
  const now = Date.now;
100
- const { body, response } = await postJson(fetcher, `${instanceUrl}/api/auth/device/code`, { client_id: CLIENT_ID });
104
+ const storedOrgs = Object.keys((await readCredentialStore()).instances[normalizeInstanceUrl(instanceUrl)] ?? {});
105
+ let orgCode = options.expectedOrg ?? process.env.HITCH_DEFAULT_ORG ?? (storedOrgs.length === 1 ? storedOrgs[0] : void 0);
106
+ if (orgCode === void 0 && process.stdin.isTTY === true) {
107
+ const terminal = readline.createInterface({
108
+ input: process.stdin,
109
+ output: process.stdout
110
+ });
111
+ try {
112
+ orgCode = (await terminal.question("Organization code: ")).trim();
113
+ } finally {
114
+ terminal.close();
115
+ }
116
+ }
117
+ if (!orgCode) throw new AuthenticationError("Select an organization with --org or HITCH_DEFAULT_ORG.");
118
+ const organizationUrl = organizationInstanceUrl(instanceUrl, orgCode);
119
+ const resource = `${organizationUrl}/mcp`;
120
+ const { body, response } = await postJson(fetcher, `${instanceUrl}/api/auth/device/code`, {
121
+ client_id: CLIENT_ID,
122
+ scope: "openid offline_access",
123
+ resource
124
+ });
101
125
  if (!response.ok) throw new AuthenticationError(typeof body.error_description === "string" ? body.error_description : `Could not start sign-in (${response.status}).`);
102
126
  const device = parseDeviceCode(body);
103
- const verificationUrl = options.expectedOrg === void 0 ? device.verification_uri_complete : `${device.verification_uri_complete}&org=${encodeURIComponent(options.expectedOrg)}`;
127
+ const verificationUrl = device.verification_uri_complete;
104
128
  console.log(`Open ${verificationUrl} and confirm code ${device.user_code}.`);
105
129
  if (!options.noBrowser) try {
106
130
  defaultOpenUrl(verificationUrl);
107
131
  } catch {}
108
- const token = await pollForToken(fetcher, instanceUrl, device, now, (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
109
- const { body: organizationBody, response: organizationResponse } = await postJson(fetcher, `${instanceUrl}/api/auth/device/organization`, { device_code: device.device_code });
110
- if (!organizationResponse.ok) throw new AuthenticationError("The sign-in did not resolve an organization.");
111
- const organization = parseDeviceOrganization(organizationBody);
112
- const me = await fetchMe(fetcher, instanceUrl, {
132
+ const token = await pollForToken(fetcher, instanceUrl, resource, device, now, (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
133
+ const me = await fetchMe(fetcher, organizationUrl, {
113
134
  accessToken: token.access_token,
114
135
  credentialType: "session",
115
136
  source: "store"
116
137
  });
117
- const membership = me.memberships.find(({ orgId }) => orgId === organization.organizationId);
118
- if (membership === void 0) throw new AuthenticationError("The device session is not scoped to a Hitch organization.");
119
- const credentials = {
120
- type: "session",
138
+ const membership = me.memberships.find(({ code }) => code === orgCode);
139
+ if (membership === void 0) throw new AuthenticationError(`You are not a member of the Hitch organization "${orgCode}".`);
140
+ const credentials = await saveOAuthCredentials(instanceUrl, {
141
+ type: "oauth",
121
142
  accessToken: token.access_token,
122
- expiresAt: token.expires_in === void 0 ? void 0 : now() + token.expires_in * 1e3,
123
- organizationId: organization.organizationId,
124
- orgCode: organization.orgCode
125
- };
126
- await setCredentials(instanceUrl, credentials);
127
- if (options.expectedOrg !== void 0 && organization.orgCode !== options.expectedOrg) throw new AuthenticationError(`logged in to "${organization.orgCode}" but this applet targets "${options.expectedOrg}" — confirm the right organization on the device page`);
143
+ expiresAt: now() + token.expires_in * 1e3,
144
+ organizationId: membership.orgId,
145
+ orgCode
146
+ }, token.refresh_token);
147
+ const storageMessage = credentialStorageMessage();
148
+ if (storageMessage !== void 0) console.log(storageMessage);
128
149
  return {
129
150
  credentials,
130
151
  me,
131
152
  membership
132
153
  };
133
154
  }
134
- //#endregion
135
- export { loginWithDevice as t };
155
+ export { parseMe as n, loginWithDevice as t };
@@ -1,6 +1,5 @@
1
1
  import { ContractProcedure, Meta, Schema } from "@orpc/contract";
2
2
  import { z } from "zod";
3
- //#region src/hitch/email.d.ts
4
3
  type EmailTags = Record<string, string>;
5
4
  type CreateEmailPixelInput = {
6
5
  tags?: EmailTags;
@@ -175,8 +174,6 @@ type HitchEmailAttachmentOutput = {
175
174
  contentType: string;
176
175
  sizeBytes: number;
177
176
  };
178
- //#endregion
179
- //#region src/server/telephony-protocol.d.ts
180
177
  /** Handle events at least once and without order. Use eventId for idempotency.
181
178
  * Read the message with hitch.telephony.messages.get({ id: messageId }). */
182
179
  type TelephonyMessageReceivedEvent = {
@@ -186,8 +183,6 @@ type TelephonyMessageReceivedEvent = {
186
183
  numberId: string;
187
184
  occurredAt: string;
188
185
  };
189
- //#endregion
190
- //#region src/server/email-protocol.d.ts
191
186
  /** Handle events at least once and without order. Use eventId for idempotency.
192
187
  * Read the message with hitch.email.messages.get({ id: messageId }). */
193
188
  type EmailMessageReceivedEvent = {
@@ -198,5 +193,4 @@ type EmailMessageReceivedEvent = {
198
193
  threadId: string;
199
194
  occurredAt: string;
200
195
  };
201
- //#endregion
202
196
  export { EmailTracker as a, HitchEmailMessage as c, ListEmailTrackersInput as d, ListEmailTrackersOutput as f, emailContract as g, SendHitchEmailInput as h, EmailContract as i, HitchEmailThread as l, ListHitchEmailThreadsInput as m, TelephonyMessageReceivedEvent as n, HitchEmailAddress as o, ListHitchEmailMessagesInput as p, CreateEmailPixelInput as r, HitchEmailAttachment as s, EmailMessageReceivedEvent as t, HitchMailbox as u };
@@ -1,19 +1,20 @@
1
- import { a as organizationInstanceUrl, c as resolveInstanceUrl } from "./store-B8N_8JIN.js";
2
- import { t as fetchWithOrgAuth } from "./auth-D3CrCtZq.js";
3
- import { t as loadAppletConfig } from "./load-config-BYrBjPlr.js";
4
- import { i as loadGitHubActionsBuildInputs, n as githubActionsContext } from "./ci-B5-mH-YW.js";
5
- import { r as prepareAppletProject } from "./project-CWVwic2C.js";
1
+ import { l as organizationInstanceUrl, m as resolveInstanceUrl } from "./store-E5NhITHl.js";
2
+ import { n as findAppletRoot } from "./workspace-CtIKSgMD.js";
3
+ import { n as fetchWithOrgAuth } from "./auth-DuMRqSGe.js";
4
+ import { t as loadAppletConfig } from "./load-config-Cffvj-aM.js";
5
+ import { i as prepareAppletProject, o as userAgent } from "./project-DZGk765E.js";
6
+ import { i as loadGitHubActionsBuildInputs, n as githubActionsContext, r as hitchResponseError } from "./ci-oWlUJKm9.js";
6
7
  import { createRequire } from "node:module";
7
8
  import { isContractProcedure, minifyContractRouter } from "@orpc/contract";
8
9
  import { randomUUID } from "node:crypto";
10
+ import { pathToFileURL } from "node:url";
9
11
  import { existsSync } from "node:fs";
10
12
  import { access, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
11
13
  import { dirname, resolve } from "node:path";
12
- import { pathToFileURL } from "node:url";
13
14
  import { execFile } from "node:child_process";
14
15
  import { promisify } from "node:util";
15
16
  import { createClient } from "@hey-api/openapi-ts";
16
- //#region src/generated.ts
17
+ // src/generated.ts
17
18
  const GENERATED_HEADER = "// Generated by @hitch42/applet — do not edit";
18
19
  async function writeGenerated(path, source) {
19
20
  await mkdir(dirname(path), { recursive: true });
@@ -57,22 +58,34 @@ import { createWorkerEntry } from "@hitch42/applet/_internal/server";
57
58
  export default createWorkerEntry(server, { code: ${JSON.stringify(appletCode)} });
58
59
  `);
59
60
  }
60
- //#endregion
61
- //#region src/typegen/operations.ts
62
- const RECORD_ROUTE = /^\/v1\/([^/]+)(?:\/(\{id\}(?:\/history)?|query|update|aggregate))?$/;
61
+ // src/typegen/create-schema.ts
62
+ function isObject$3(value) {
63
+ return value !== null && typeof value === "object" && !Array.isArray(value);
64
+ }
65
+ function isComponentRef(value) {
66
+ return isObject$3(value) && typeof value.$ref === "string" && /^#\/components\/schemas\/[^/]+$/.test(value.$ref);
67
+ }
68
+ function singleCreateSchema(value) {
69
+ if (isComponentRef(value)) return value;
70
+ if (!isObject$3(value) || !Array.isArray(value.oneOf) || value.oneOf.length !== 2) return void 0;
71
+ const [single, array] = value.oneOf;
72
+ return isComponentRef(single) && isObject$3(array) && array.type === "array" && isComponentRef(array.items) && array.items.$ref === single.$ref ? single : void 0;
73
+ }
74
+ // src/typegen/operations.ts
75
+ const RECORD_ROUTE = /^\/v1\/([^/]+)(?:\/(\{record\}(?:\/history)?|query|bulk-update|aggregate))?$/;
63
76
  const COMPONENT_REF_PREFIX$1 = "#/components/schemas/";
64
- const BATCH_PATH = "/v1/batch";
65
- const BATCH_ITEM_PATH = "paths./v1/batch.post.requestBody.content.application/json.schema.properties.requests.items";
77
+ const BATCH_PATH = "/v1/batch/run";
78
+ const BATCH_ITEM_PATH = "paths./v1/batch/run.post.requestBody.content.application/json.schema.properties.requests.items";
66
79
  const BATCH_ID_PATH = /^\^\/v1\/([^/]+)\/\[0-9a-fA-F-\]\{36\}\$$/;
67
80
  const VERBS = {
68
81
  "get:": "list",
69
82
  "post:": "create",
70
- "get:{id}": "get",
71
- "get:{id}/history": "history",
72
- "patch:{id}": "update",
73
- "delete:{id}": "delete",
83
+ "get:{record}": "get",
84
+ "get:{record}/history": "history",
85
+ "patch:{record}": "update",
86
+ "delete:{record}": "delete",
74
87
  "post:query": "query",
75
- "post:update": "updateWhere",
88
+ "post:bulk-update": "updateWhere",
76
89
  "post:aggregate": "aggregate"
77
90
  };
78
91
  const COMPONENT_RESPONSE_STATUS = {
@@ -89,7 +102,7 @@ function isObject$2(value) {
89
102
  function recordOperation(path, method) {
90
103
  const match = RECORD_ROUTE.exec(path);
91
104
  const objectCode = match?.[1];
92
- if (objectCode === void 0 || objectCode === "batch") return void 0;
105
+ if (objectCode === void 0 || objectCode === "objects") return void 0;
93
106
  const key = `${method.toLowerCase()}:${match?.at(2) ?? ""}`;
94
107
  if (!Object.hasOwn(VERBS, key)) return void 0;
95
108
  return {
@@ -99,7 +112,7 @@ function recordOperation(path, method) {
99
112
  }
100
113
  function recordObjectCode(path) {
101
114
  const match = RECORD_ROUTE.exec(path);
102
- return match?.[1] === "batch" ? void 0 : match?.[1];
115
+ return match?.[1] === "objects" ? void 0 : match?.[1];
103
116
  }
104
117
  function withRecordOperationId(path, method, operation) {
105
118
  const record = recordOperation(path, method);
@@ -116,13 +129,13 @@ function batchEntry(entry, index) {
116
129
  const enumPath = Array.isArray(path?.enum) && path.enum.length === 1 ? path.enum[0] : void 0;
117
130
  if (Array.isArray(method) && method.length !== 1 || Array.isArray(path?.enum) && path.enum.length !== 1) throw new Error(`${BATCH_ITEM_PATH}.oneOf[${index}] expected a create, update, delete, or updateWhere batch entry, found ${describeValue$1(entry)}`);
118
131
  if (methodValue === "POST" && typeof enumPath === "string") {
119
- const updateWhere = /^\/v1\/([^/]+)\/update$/.exec(enumPath);
132
+ const updateWhere = /^\/v1\/([^/]+)\/bulk-update$/.exec(enumPath);
120
133
  if (updateWhere?.[1] !== void 0) return {
121
134
  objectCode: updateWhere[1],
122
135
  verb: "updateWhere"
123
136
  };
124
137
  const create = /^\/v1\/([^/]+)$/.exec(enumPath);
125
- if (create?.[1] !== void 0 && create[1] !== "batch") return {
138
+ if (create?.[1] !== void 0) return {
126
139
  objectCode: create[1],
127
140
  verb: "create"
128
141
  };
@@ -195,9 +208,15 @@ function prepareRecordSpec(spec, manifest) {
195
208
  const content = isObject$2(response?.content) ? response.content : void 0;
196
209
  const mediaType = isObject$2(content?.["application/json"]) ? content["application/json"] : void 0;
197
210
  const schema = isObject$2(mediaType) ? mediaType.schema : void 0;
198
- const ref = isObject$2(schema) ? schema.$ref : void 0;
211
+ const ref = record.verb === "create" ? singleCreateSchema(schema)?.$ref : isObject$2(schema) ? schema.$ref : void 0;
199
212
  if (typeof ref !== "string" || !ref.startsWith(COMPONENT_REF_PREFIX$1) || ref.slice(21).length === 0 || ref.slice(21).includes("/")) throw new Error(`object "${record.objectCode}": paths.${path}.${method}.responses.${status}.content.application/json.schema expected a component schema $ref, found ${describeValue$1(schema, true)}`);
200
213
  }
214
+ if (record.verb === "create") {
215
+ const request = isObject$2(operation.requestBody) ? operation.requestBody : void 0;
216
+ const content = isObject$2(request?.content) ? request.content : void 0;
217
+ const body = isObject$2(content?.["application/json"]) ? content["application/json"] : void 0;
218
+ if (singleCreateSchema(body?.schema) === void 0) throw new Error(`object "${record.objectCode}": paths.${path}.${method}.requestBody.content.application/json.schema expected a component schema $ref or single/array oneOf, found ${describeValue$1(body?.schema, true)}`);
219
+ }
201
220
  const prepared = withRecordOperationId(path, method, operation);
202
221
  return prepared ? [[method, prepared]] : [];
203
222
  });
@@ -205,7 +224,7 @@ function prepareRecordSpec(spec, manifest) {
205
224
  }));
206
225
  manifest?.objects.forEach(({ code, optionalAttributes }) => {
207
226
  if (optionalAttributes === void 0 || optionalAttributes.length === 0) return;
208
- const responsePath = `/v1/${code}/{id}`;
227
+ const responsePath = `/v1/${code}/{record}`;
209
228
  const response = prepared.paths[responsePath]?.get?.responses;
210
229
  const schema = isObject$2(response) && isObject$2(response["200"]) ? response["200"].content : void 0;
211
230
  const mediaType = isObject$2(schema) && isObject$2(schema["application/json"]) ? schema["application/json"] : void 0;
@@ -243,8 +262,7 @@ function describeValue$1(value, expectedRef = false) {
243
262
  if (typeof value === "object") return expectedRef ? "an object without a valid $ref" : "an object";
244
263
  return `${typeof value} ${JSON.stringify(value)}`;
245
264
  }
246
- //#endregion
247
- //#region src/typegen/catalog.ts
265
+ // src/typegen/catalog.ts
248
266
  const COMPONENT_REF_PREFIX = "#/components/schemas/";
249
267
  const RESERVED_ATTRIBUTE_CODES = /* @__PURE__ */ new Set([
250
268
  "id",
@@ -262,7 +280,7 @@ function describeValue(value, objectDescription = "an object") {
262
280
  return `${typeof value} ${JSON.stringify(value)}`;
263
281
  }
264
282
  function componentName(value, path, objectCode) {
265
- const ref = isObject$1(value) ? value.$ref : void 0;
283
+ const ref = singleCreateSchema(value)?.$ref;
266
284
  if (typeof ref !== "string" || !ref.startsWith(COMPONENT_REF_PREFIX)) throw new Error(`object "${objectCode}": ${path} expected a component schema $ref, found ${describeValue(value, "an object without a $ref")}`);
267
285
  const name = ref.slice(21);
268
286
  if (name.length === 0 || name.includes("/")) throw new Error(`object "${objectCode}": ${path} expected a direct component schema $ref, found ${JSON.stringify(ref)}`);
@@ -290,8 +308,7 @@ function catalogFromSpec(spec) {
290
308
  return [[operation.objectCode, Object.keys(schema.properties).filter((code) => !RESERVED_ATTRIBUTE_CODES.has(code))]];
291
309
  }));
292
310
  }
293
- //#endregion
294
- //#region src/typegen/guard.ts
311
+ // src/typegen/guard.ts
295
312
  function isObject(value) {
296
313
  return value !== null && typeof value === "object" && !Array.isArray(value);
297
314
  }
@@ -306,8 +323,7 @@ function guardManifestForSpec(spec, manifest) {
306
323
  const missing = manifest.objects.find(({ code }) => !available.includes(code));
307
324
  if (missing !== void 0) throw new Error(`object "${missing.code}" not found in org spec (available: ${available.join(", ")})`);
308
325
  }
309
- //#endregion
310
- //#region src/typegen/register.ts
326
+ // src/typegen/register.ts
311
327
  function registerFile(catalog, options, publicApplet) {
312
328
  const catalogType = catalog === void 0 ? "type Catalog = Record<string, string>;" : `type Catalog = {
313
329
  ${Object.entries(catalog).map(([objectCode, attributeCodes]) => ` ${JSON.stringify(objectCode)}: ${attributeCodes.map((code) => JSON.stringify(code)).join(" | ") || "never"};`).join("\n")}
@@ -371,8 +387,7 @@ async function writeRegisterDeclaration(outDir, catalog, publicApplet = false) {
371
387
  hasServer: existsSync(resolve(outDir, "../src/server/main.ts"))
372
388
  }, publicApplet));
373
389
  }
374
- //#endregion
375
- //#region src/typegen/generate.ts
390
+ // src/typegen/generate.ts
376
391
  const execFileAsync = promisify(execFile);
377
392
  const require = createRequire(import.meta.url);
378
393
  function routesModuleSource(routes) {
@@ -427,8 +442,12 @@ async function responseJson(response) {
427
442
  async function loadAuthenticatedSpec(manifest) {
428
443
  const github = githubActionsContext();
429
444
  if (github !== void 0) return (await loadGitHubActionsBuildInputs(github, manifest.requiresChangeset)).openapi;
430
- const response = await fetchWithOrgAuth(manifest.org, (instanceUrl, accessToken) => globalThis.fetch(`${instanceUrl}/api/v1/openapi.json${manifest.requiresChangeset === void 0 ? "" : `?changeset_id=${encodeURIComponent(manifest.requiresChangeset)}`}`, { headers: { Authorization: `Bearer ${accessToken}` } }));
431
- if (!response.ok) throw new Error(`Failed to load the organization's OpenAPI spec (${response.status}).`);
445
+ const agent = await userAgent();
446
+ const response = await fetchWithOrgAuth(manifest.org, (instanceUrl, accessToken) => globalThis.fetch(`${instanceUrl}/api/v1/openapi.json${manifest.requiresChangeset === void 0 ? "" : `?changeset_id=${encodeURIComponent(manifest.requiresChangeset)}`}`, { headers: {
447
+ Authorization: `Bearer ${accessToken}`,
448
+ "User-Agent": agent
449
+ } }));
450
+ if (!response.ok) throw await hitchResponseError(response, "Failed to load the organization's OpenAPI spec");
432
451
  return responseJson(response);
433
452
  }
434
453
  async function loadThemeStylesheet(orgCode) {
@@ -491,7 +510,7 @@ async function writeContractFiles(outDir, manifest, spec) {
491
510
  await generateContract(preparedSpec, outDir);
492
511
  await writeManifestGen(outDir, manifest);
493
512
  await writeRegisterDeclaration(outDir, catalog, manifest.public === true);
494
- const hasBatch = Object.hasOwn(preparedSpec.paths, "/v1/batch");
513
+ const hasBatch = Object.hasOwn(preparedSpec.paths, "/v1/batch/run");
495
514
  await writeFile(resolve(outDir, "index.ts"), `// Generated by applet generate — do not edit.
496
515
  import { emailContract, filesContract as files, meContract as me, pruneContract, registerHitchContract, teamsContract as teams, telephonyContract as telephony } from "@hitch42/applet";
497
516
  import manifest from "./manifest.gen.ts";
@@ -521,11 +540,11 @@ async function syncContract(outDir, manifest) {
521
540
  }
522
541
  async function runGenerate() {
523
542
  await prepareAppletProject();
524
- const root = process.cwd();
525
- const outDir = resolve("./.hitch");
543
+ const root = findAppletRoot(process.cwd()) ?? process.cwd();
544
+ const outDir = resolve(root, ".hitch");
526
545
  const manifest = await loadAppletConfig(process.cwd());
527
546
  const { spec, theme: themeStylesheet } = await loadBuildInputs(manifest);
528
- const stagedDir = await mkdtemp(resolve("./.hitch.tmp-"));
547
+ const stagedDir = await mkdtemp(resolve(root, ".hitch.tmp-"));
529
548
  const backupDir = `${stagedDir}.backup`;
530
549
  let staged = true;
531
550
  let backedUp = false;
@@ -571,5 +590,4 @@ async function runGenerate() {
571
590
  if (backedUp && !await fileExists(outDir)) await rename(backupDir, outDir);
572
591
  }
573
592
  }
574
- //#endregion
575
593
  export { writeRegisterDeclaration as a, writeAppletRoot as c, writeServerEntry as d, syncContract as i, writeClientEntry as l, routesModuleSource as n, prepareRecordSpec as o, runGenerate as r, recordOperation as s, generateContract as t, writeManifestGen as u };
@@ -0,0 +1 @@
1
+ export { r as runGenerate } from "./generate-48pqFh5M.js";
@@ -0,0 +1,215 @@
1
+ import { r as findParentRoot } from "./workspace-CtIKSgMD.js";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { fileURLToPath } from "node:url";
4
+ import { existsSync } from "node:fs";
5
+ import fs$1 from "node:fs/promises";
6
+ import { dirname, relative, resolve } from "node:path";
7
+ // src/atomic-file.ts
8
+ async function writeChangedFile(path, contents) {
9
+ const expected = Buffer.isBuffer(contents) ? contents : Buffer.from(contents);
10
+ if ((await fs$1.readFile(path).catch((error) => {
11
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
12
+ throw error;
13
+ }))?.equals(expected) && !(await fs$1.lstat(path)).isSymbolicLink()) return;
14
+ const temporary = `${path}.${randomUUID()}.tmp`;
15
+ try {
16
+ await fs$1.writeFile(temporary, expected);
17
+ await fs$1.rename(temporary, path);
18
+ } finally {
19
+ await fs$1.rm(temporary, { force: true });
20
+ }
21
+ }
22
+ // src/generated-agents.ts
23
+ const GENERATED_SKILLS = [
24
+ "hitch-applets",
25
+ "hitch-cli",
26
+ "hitch-data",
27
+ "hitch-theory",
28
+ "react-best-practices",
29
+ "tanstack-query",
30
+ "tanstack-router",
31
+ "web-design-guidelines"
32
+ ];
33
+ const PARENT_GUIDE = `# Hitch applet parent\n\nEach child folder is one applet repo. Run applet commands in a child for that applet.\nRun applet deploy or applet bootstrap in this parent for all children. Commit changes in each child separately.\n\n`;
34
+ function agentFileAction(input) {
35
+ if (input.child) {
36
+ if (input.kind !== "missing" && !input.dangling && !input.generated) return "edited";
37
+ return input.kind === "link" && input.correctLink && !input.dangling ? "keep" : "link";
38
+ }
39
+ return "write";
40
+ }
41
+ async function state(path) {
42
+ try {
43
+ return (await fs$1.lstat(path)).isSymbolicLink() ? "link" : "file";
44
+ } catch (error) {
45
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return "missing";
46
+ throw error;
47
+ }
48
+ }
49
+ async function files(root) {
50
+ const entries = await fs$1.readdir(root, { withFileTypes: true });
51
+ const contents = await Promise.all(entries.map(async (entry) => {
52
+ if (entry.isDirectory()) return [...await files(resolve(root, entry.name))].map(([path, bytes]) => [`${entry.name}/${path}`, bytes]);
53
+ if (entry.isSymbolicLink()) return [[entry.name, Buffer.from(`symlink:${await fs$1.readlink(resolve(root, entry.name))}`)]];
54
+ return [[entry.name, await fs$1.readFile(resolve(root, entry.name))]];
55
+ }));
56
+ return new Map(contents.flat());
57
+ }
58
+ async function replaceLink(path, target, directory = false) {
59
+ const kind = await state(path);
60
+ if (kind === "link" && await fs$1.readlink(path) === target) return;
61
+ await fs$1.mkdir(dirname(path), { recursive: true });
62
+ const temporary = `${path}.${randomUUID()}.tmp`;
63
+ try {
64
+ await fs$1.symlink(target, temporary, directory ? "dir" : "file");
65
+ if (kind === "file" && directory) await fs$1.rm(path, {
66
+ recursive: true,
67
+ force: true
68
+ });
69
+ await fs$1.rename(temporary, path);
70
+ } finally {
71
+ await fs$1.rm(temporary, { force: true });
72
+ }
73
+ }
74
+ async function generatedAgentContents() {
75
+ const agents = await fs$1.readFile(new URL("../scaffold/AGENTS.md", import.meta.url));
76
+ const result = /* @__PURE__ */ new Map([["AGENTS.md", agents], ["CLAUDE.md", agents]]);
77
+ await Promise.all(GENERATED_SKILLS.map(async (skill) => {
78
+ (await files(fileURLToPath(new URL(`../scaffold/.agents/skills/${skill}`, import.meta.url)))).forEach((bytes, name) => {
79
+ result.set(`.agents/skills/${skill}/${name}`, bytes);
80
+ result.set(`.claude/skills/${skill}/${name}`, bytes);
81
+ });
82
+ }));
83
+ return result;
84
+ }
85
+ const editedPaths = /* @__PURE__ */ new Set();
86
+ async function writeGeneratedAgents(root) {
87
+ const parent = findParentRoot(dirname(root));
88
+ const home = parent ?? root;
89
+ const parentGuide = parent !== void 0 || existsSync(resolve(root, ".hitchroot"));
90
+ const agents = await fs$1.readFile(new URL("../scaffold/AGENTS.md", import.meta.url), "utf8");
91
+ const guide = `${parentGuide ? PARENT_GUIDE : ""}${agents}`;
92
+ await fs$1.mkdir(home, { recursive: true });
93
+ const inventoryPath = resolve(home, ".agents/.hitch-generated.json");
94
+ const previous = JSON.parse(await fs$1.readFile(inventoryPath, "utf8").catch((error) => {
95
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return "{}";
96
+ throw error;
97
+ }));
98
+ const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
99
+ const generated = await Promise.all(GENERATED_SKILLS.map(async (skill) => ({
100
+ skill,
101
+ contents: await files(fileURLToPath(new URL(`../scaffold/.agents/skills/${skill}`, import.meta.url)))
102
+ })));
103
+ const editedLinks = /* @__PURE__ */ new Set();
104
+ await Promise.all(generated.map(async ({ skill, contents }) => {
105
+ const path = resolve(home, ".agents/skills", skill);
106
+ if (await state(path) !== "link") return;
107
+ const current = await files(path).catch((error) => {
108
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
109
+ throw error;
110
+ });
111
+ if (current !== void 0 && (current.size !== contents.size || [...contents].some(([name, bytes]) => !current.get(name)?.equals(bytes)))) {
112
+ if (!editedPaths.has(path)) console.warn(`${path} was edited; Hitch left it in place.`);
113
+ editedPaths.add(path);
114
+ editedLinks.add(skill);
115
+ return;
116
+ }
117
+ await fs$1.unlink(path).catch((error) => {
118
+ if (!(error instanceof Error && "code" in error && ["ENOENT", "EISDIR"].includes(String(error.code)))) throw error;
119
+ });
120
+ }));
121
+ const expected = /* @__PURE__ */ new Map([["AGENTS.md", Buffer.from(guide)]]);
122
+ await Promise.all(generated.map(async ({ skill, contents }) => {
123
+ const realClaude = await state(resolve(home, ".claude/skills", skill)) === "file";
124
+ contents.forEach((bytes, name) => {
125
+ if (!editedLinks.has(skill)) expected.set(`.agents/skills/${skill}/${name}`, bytes);
126
+ if (realClaude) expected.set(`.claude/skills/${skill}/${name}`, bytes);
127
+ });
128
+ }));
129
+ const inventory = {};
130
+ await Promise.all([...expected].map(async ([name, bytes]) => {
131
+ const path = resolve(home, name);
132
+ const current = await state(path) === "missing" ? void 0 : await fs$1.readFile(path).catch((error) => {
133
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
134
+ throw error;
135
+ });
136
+ if (!(current === void 0 || current.equals(bytes) || digest(current) === previous[name] || name === "AGENTS.md" && current.toString() === agents)) {
137
+ if (!editedPaths.has(path)) console.warn(`${path} was edited; Hitch left it in place.`);
138
+ editedPaths.add(path);
139
+ return;
140
+ }
141
+ await fs$1.mkdir(dirname(path), { recursive: true });
142
+ await writeChangedFile(path, bytes);
143
+ inventory[name] = digest(bytes);
144
+ }));
145
+ await Promise.all(Object.entries(previous).filter(([name]) => !expected.has(name)).map(async ([name, hash]) => {
146
+ if ([...editedLinks].some((skill) => name.startsWith(`.agents/skills/${skill}/`))) return;
147
+ if (![".agents", ".claude"].some((directory) => GENERATED_SKILLS.some((skill) => name.startsWith(`${directory}/skills/${skill}/`)))) return;
148
+ const path = resolve(home, name);
149
+ if (await state(path) === "file" && digest(await fs$1.readFile(path)) === hash) await fs$1.rm(path, { force: true });
150
+ }));
151
+ const claude = resolve(home, "CLAUDE.md");
152
+ const claudeContents = await state(claude) === "missing" ? void 0 : await fs$1.readFile(claude, "utf8").catch((error) => {
153
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
154
+ throw error;
155
+ });
156
+ if (claudeContents === void 0 || claudeContents === agents || claudeContents === guide) await replaceLink(claude, "AGENTS.md");
157
+ else if (!editedPaths.has(claude)) {
158
+ console.warn(`${claude} was edited; Hitch left it in place.`);
159
+ editedPaths.add(claude);
160
+ }
161
+ await Promise.all(generated.map(async ({ skill, contents }) => {
162
+ const path = resolve(home, ".claude/skills", skill);
163
+ const kind = await state(path);
164
+ const target = `../../.agents/skills/${skill}`;
165
+ if (kind === "file") return;
166
+ if (kind === "link" && await fs$1.readlink(path) !== target) {
167
+ const current = await files(path).catch((error) => {
168
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
169
+ throw error;
170
+ });
171
+ if (current !== void 0 && (current.size !== contents.size || [...contents].some(([name, bytes]) => !current.get(name)?.equals(bytes)))) {
172
+ if (!editedPaths.has(path)) console.warn(`${path} was edited; Hitch left it in place.`);
173
+ editedPaths.add(path);
174
+ return;
175
+ }
176
+ }
177
+ await replaceLink(path, target, true);
178
+ }));
179
+ await fs$1.mkdir(dirname(inventoryPath), { recursive: true });
180
+ await writeChangedFile(inventoryPath, `${JSON.stringify(Object.fromEntries(Object.entries(inventory).sort(([a], [b]) => a.localeCompare(b))), null, 2)}\n`);
181
+ if (parent === void 0) return;
182
+ const paths = [
183
+ "AGENTS.md",
184
+ "CLAUDE.md",
185
+ ...GENERATED_SKILLS.flatMap((skill) => [`.agents/skills/${skill}`, `.claude/skills/${skill}`])
186
+ ];
187
+ for (const name of paths) {
188
+ const path = resolve(root, name);
189
+ const kind = await state(path);
190
+ const target = relative(dirname(path), resolve(home, name));
191
+ let equal = true;
192
+ if (kind === "file" || kind === "link" && existsSync(path)) {
193
+ if (name.endsWith(".md")) {
194
+ const current = await fs$1.readFile(path, "utf8");
195
+ equal = current === agents || current === guide;
196
+ } else {
197
+ const current = await files(path);
198
+ const expected = generated.find(({ skill }) => name.endsWith(`/${skill}`)).contents;
199
+ equal = current.size === expected.size && [...expected].every(([file, bytes]) => current.get(file)?.equals(bytes));
200
+ }
201
+ }
202
+ const action = agentFileAction({
203
+ child: true,
204
+ kind,
205
+ generated: equal,
206
+ dangling: kind === "link" && !existsSync(path),
207
+ correctLink: kind === "link" && await fs$1.readlink(path) === target
208
+ });
209
+ if (action === "edited") {
210
+ if (!editedPaths.has(path)) console.warn(`${path} was edited; Hitch left it in place.`);
211
+ editedPaths.add(path);
212
+ } else if (action === "link") await replaceLink(path, target, !name.endsWith(".md"));
213
+ }
214
+ }
215
+ export { generatedAgentContents as n, writeGeneratedAgents as r, GENERATED_SKILLS as t };
@@ -0,0 +1,24 @@
1
+ import { l as organizationInstanceUrl, p as resolveAuthForOrg, r as LoginRequiredError, t as AuthenticationError } from "./store-E5NhITHl.js";
2
+ // src/git-credential.ts
3
+ function parseGitCredentialHost(input, instanceUrl) {
4
+ const host = input.split(/\r?\n/).find((line) => line.startsWith("host="))?.slice(5);
5
+ if (host === void 0) throw new AuthenticationError("Git did not provide a host.");
6
+ const instance = new URL(instanceUrl);
7
+ const labels = instance.hostname.split(".");
8
+ const hostLabels = host.split(":")[0].split(".");
9
+ const orgCode = hostLabels[0];
10
+ if (labels[0] !== "app" || hostLabels.slice(1).join(".") !== labels.slice(1).join(".") || host.includes(":") && host.slice(host.lastIndexOf(":")) !== `:${instance.port}`) throw new AuthenticationError("Git requested credentials for a different Hitch host.");
11
+ if (new URL(organizationInstanceUrl(instanceUrl, orgCode)).host !== host) throw new AuthenticationError("Git requested credentials for a different Hitch host.");
12
+ return orgCode;
13
+ }
14
+ async function runGitCredential(action, instanceUrl) {
15
+ if (action !== "get" && action !== "store" && action !== "erase") throw new Error("Use applet git-credential <get|store|erase>.");
16
+ if (action !== "get") return;
17
+ let input = "";
18
+ for await (const chunk of process.stdin) input += String(chunk);
19
+ const orgCode = parseGitCredentialHost(input, instanceUrl);
20
+ const auth = await resolveAuthForOrg(instanceUrl, orgCode);
21
+ if (auth === void 0) throw new LoginRequiredError();
22
+ process.stdout.write(`username=hitch\npassword=${auth.accessToken}\n\n`);
23
+ }
24
+ export { runGitCredential };