@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.
- package/dist/_internal/client.d.ts +0 -2
- package/dist/_internal/client.js +3 -5
- package/dist/_internal/log-context.d.ts +0 -2
- package/dist/_internal/log-context.js +1 -2
- package/dist/_internal/server.d.ts +2 -10
- package/dist/_internal/server.js +9 -14
- package/dist/applet.js +64 -23
- package/dist/auth-DuMRqSGe.js +49 -0
- package/dist/{batch-_ZjAg-Kk.js → batch-CiLZ177G.js} +3 -4
- package/dist/build-C7hi2yg4.js +1 -0
- package/dist/{build-DkKTiIu7.js → build-FAQSHJgm.js} +4 -3
- package/dist/{ci-B5-mH-YW.js → ci-oWlUJKm9.js} +10 -6
- package/dist/client/hitch/client.d.ts +1 -1
- package/dist/client/hitch/client.js +1 -2
- package/dist/client/hitch/phone.d.ts +1 -1
- package/dist/client/hitch/phone.js +1 -2
- package/dist/client/hitch.d.ts +2 -2
- package/dist/client/hitch.js +2 -2
- package/dist/client/react/markdown-editor.d.ts +1 -1
- package/dist/client/react/markdown-editor.js +1 -2
- package/dist/client/react/markdown.d.ts +1 -1
- package/dist/client/react/markdown.js +1 -2
- package/dist/client/react.d.ts +2 -2
- package/dist/client/react.js +2 -2
- package/dist/client.d.ts +2 -4
- package/dist/client.js +3 -5
- package/dist/{contract-CJv-n1l3.d.ts → contract-CUc0B2rl.d.ts} +1 -3
- package/dist/{contract-BeKz1Qcs.js → contract-D9LCNGjD.js} +1 -2
- package/dist/create-BJqLT8Uq.d.ts +68 -0
- package/dist/{deploy-DODTE8ri.js → deploy-BM55ZoSp.js} +160 -152
- package/dist/{device-BgIfytaR.js → device-CZWWckCq.js} +54 -34
- package/dist/{email-protocol-B9u_dh2p.d.ts → email-protocol-CEqzASSx.d.ts} +0 -6
- package/dist/{generate-8yyxKRSz.js → generate-48pqFh5M.js} +57 -39
- package/dist/generate-BYK4aNud.js +1 -0
- package/dist/generated-agents-BUnJ0FQA.js +215 -0
- package/dist/git-credential-CU7-Erzs.js +24 -0
- package/dist/git-deploy-C12nMNB6.js +290 -0
- package/dist/git-deploy-CNz6mxiF.js +1 -0
- package/dist/git-migration-CIq-sN61.js +629 -0
- package/dist/git-safety-Ck3X7jvA.js +13 -0
- package/dist/{headers-CeRX-Xji.js → headers-BcEMIVCo.js} +1 -2
- package/dist/hitch-client-D33JOEOi.d.ts +22 -0
- package/dist/{hitch-client-Cw-WZ_8v.js → hitch-client-Dph_kzDd.js} +5 -7
- package/dist/{hitch-file-url-BPKj9zVG.js → hitch-file-url--AMjDOzP.js} +1 -2
- package/dist/index.d.ts +8 -15
- package/dist/index.js +32 -37
- package/dist/{init-DT0faD-q.js → init-Bb6WT8UB.js} +70 -18
- package/dist/{lazy-client-CwIzYAoE.js → lazy-client--QCL0wiu.js} +1 -2
- package/dist/{load-config-BYrBjPlr.js → load-config-Cffvj-aM.js} +27 -26
- package/dist/{log-context-DWznGjfz.js → log-context-BepwdjrR.js} +1 -2
- package/dist/login-BWZKo7Ui.js +21 -0
- package/dist/{logs-CEIikWzV.js → logs-glCVHI-8.js} +6 -6
- package/dist/{manifest-BBHD_EY9.d.ts → manifest-CpghlaJc.d.ts} +6 -7
- package/dist/manifest-patterns-BfxqImlp.js +4 -0
- package/dist/{markdown-CwlqzBtP.js → markdown-BA_XzFxq.js} +2 -3
- package/dist/{markdown-DSHOQIOb.d.ts → markdown-BXt65dDo.d.ts} +1 -3
- package/dist/{markdown-editor-BmEaBzd7.d.ts → markdown-editor-CB_nWLzj.d.ts} +1 -5
- package/dist/{markdown-editor-C5mLHGqu.js → markdown-editor-CaUuphfc.js} +3 -5
- package/dist/parent-D52Kxp8U.js +1 -0
- package/dist/parent-Zx8Eg1Ki.js +75 -0
- package/dist/{project-CWVwic2C.js → project-DZGk765E.js} +74 -58
- package/dist/{prompt-ChS-grYO.js → prompt-CW98R-Un.js} +1 -2
- package/dist/{query-cache-CXmAyLv3.js → query-cache-DQJy45RT.js} +1 -2
- package/dist/{query-utils-zz9pVjgp.d.ts → query-utils-okxIoKM3.d.ts} +0 -2
- package/dist/{schedules-B7xNl9oy.js → schedules-CSUb84e2.js} +5 -5
- package/dist/server.d.ts +6 -14
- package/dist/server.js +17 -20
- package/dist/source-hash-BbvtRaus.js +107 -0
- package/dist/store-E5NhITHl.js +448 -0
- package/dist/{upload-D_kkmne_.d.ts → upload-DXZyjZSS.d.ts} +6 -10
- package/dist/{upload-DRbVhuDm.js → upload-DtMavo8Q.js} +4 -6
- package/dist/{use-phone-ug4B5gQp.js → use-phone-CB_idmrz.js} +9 -13
- package/dist/{use-phone-M7c_HsB7.d.ts → use-phone-DBAkasPq.d.ts} +0 -4
- package/dist/{variables-DCbLJBr0.js → variables-CgZstC7I.js} +7 -8
- package/dist/vite.d.ts +1 -3
- package/dist/vite.js +42 -51
- package/dist/{workflow-DurM6Fwx.d.ts → workflow-BnBrJIzu.d.ts} +0 -2
- package/dist/{workflow-engine-BOTzURKM.js → workflow-engine-CyyaVENo.js} +2 -4
- package/dist/{workflow-CTo2kZF1.js → workflow-l2SlVePB.js} +2 -4
- package/dist/workspace-CtIKSgMD.js +76 -0
- package/package.json +6 -5
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +28 -10
- package/scaffold/.agents/skills/hitch-applets/references/files.md +2 -2
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +8 -6
- package/scaffold/.agents/skills/hitch-applets/references/performance.md +2 -2
- package/scaffold/.agents/skills/hitch-applets/references/records.md +24 -9
- package/scaffold/.agents/skills/hitch-cli/SKILL.md +52 -43
- package/scaffold/AGENTS.md +6 -0
- package/dist/auth-D3CrCtZq.js +0 -21
- package/dist/batch-Qa4B4-Yp.d.ts +0 -30
- package/dist/build-DpkWOvd2.js +0 -2
- package/dist/generate-Df29-UJm.js +0 -2
- package/dist/hitch-client--qZVa2KM.d.ts +0 -23
- package/dist/login-CceGvlMi.js +0 -14
- package/dist/store-B8N_8JIN.js +0 -196
- package/dist/workspace-8yqXsvce.js +0 -31
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { l as
|
|
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
|
-
|
|
4
|
-
|
|
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
|
|
46
|
-
|
|
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
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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(({
|
|
118
|
-
if (membership === void 0) throw new AuthenticationError(
|
|
119
|
-
const credentials = {
|
|
120
|
-
type: "
|
|
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:
|
|
123
|
-
organizationId:
|
|
124
|
-
orgCode
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
if (
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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:{
|
|
71
|
-
"get:{
|
|
72
|
-
"patch:{
|
|
73
|
-
"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 === "
|
|
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] === "
|
|
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
|
|
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}/{
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
431
|
-
|
|
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("
|
|
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("
|
|
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 };
|