@hitch42/applet 0.1.1-beta.20 → 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-D47vK5aa.js → build-FAQSHJgm.js} +4 -3
- package/dist/{ci-BTFTCBgy.js → ci-oWlUJKm9.js} +5 -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-C2UrZYSK.d.ts → create-BJqLT8Uq.d.ts} +0 -4
- package/dist/{deploy-BbqyK5Ik.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-BLD0DoCk.js → generate-48pqFh5M.js} +31 -37
- 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-DtyTZzP5.d.ts → hitch-client-D33JOEOi.d.ts} +5 -7
- 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-BKUBf6i9.js → init-Bb6WT8UB.js} +70 -18
- package/dist/{lazy-client-CwIzYAoE.js → lazy-client--QCL0wiu.js} +1 -2
- package/dist/{load-config-DGALUExv.js → load-config-Cffvj-aM.js} +24 -26
- package/dist/{log-context-DWznGjfz.js → log-context-BepwdjrR.js} +1 -2
- package/dist/login-BWZKo7Ui.js +21 -0
- package/dist/{logs-Cv5z1e2w.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-CrxdZR0B.js → project-DZGk765E.js} +63 -54
- 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-Dajgni_z.js → schedules-CSUb84e2.js} +5 -5
- package/dist/server.d.ts +5 -13
- 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-B-CLu8cY.js → variables-CgZstC7I.js} +7 -8
- package/dist/vite.d.ts +1 -3
- package/dist/vite.js +41 -50
- 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 +27 -9
- 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 +8 -8
- 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/build-JtbY8q2p.js +0 -2
- package/dist/generate-COCRy1Bs.js +0 -2
- package/dist/login-CceGvlMi.js +0 -14
- package/dist/store-B8N_8JIN.js +0 -196
- package/dist/workspace-8yqXsvce.js +0 -31
|
@@ -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 { i as
|
|
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,8 +58,7 @@ import { createWorkerEntry } from "@hitch42/applet/_internal/server";
|
|
|
57
58
|
export default createWorkerEntry(server, { code: ${JSON.stringify(appletCode)} });
|
|
58
59
|
`);
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
//#region src/typegen/create-schema.ts
|
|
61
|
+
// src/typegen/create-schema.ts
|
|
62
62
|
function isObject$3(value) {
|
|
63
63
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
64
64
|
}
|
|
@@ -71,22 +71,21 @@ function singleCreateSchema(value) {
|
|
|
71
71
|
const [single, array] = value.oneOf;
|
|
72
72
|
return isComponentRef(single) && isObject$3(array) && array.type === "array" && isComponentRef(array.items) && array.items.$ref === single.$ref ? single : void 0;
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const RECORD_ROUTE = /^\/v1\/([^/]+)(?:\/(\{id\}(?:\/history)?|query|update|aggregate))?$/;
|
|
74
|
+
// src/typegen/operations.ts
|
|
75
|
+
const RECORD_ROUTE = /^\/v1\/([^/]+)(?:\/(\{record\}(?:\/history)?|query|bulk-update|aggregate))?$/;
|
|
77
76
|
const COMPONENT_REF_PREFIX$1 = "#/components/schemas/";
|
|
78
|
-
const BATCH_PATH = "/v1/batch";
|
|
79
|
-
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";
|
|
80
79
|
const BATCH_ID_PATH = /^\^\/v1\/([^/]+)\/\[0-9a-fA-F-\]\{36\}\$$/;
|
|
81
80
|
const VERBS = {
|
|
82
81
|
"get:": "list",
|
|
83
82
|
"post:": "create",
|
|
84
|
-
"get:{
|
|
85
|
-
"get:{
|
|
86
|
-
"patch:{
|
|
87
|
-
"delete:{
|
|
83
|
+
"get:{record}": "get",
|
|
84
|
+
"get:{record}/history": "history",
|
|
85
|
+
"patch:{record}": "update",
|
|
86
|
+
"delete:{record}": "delete",
|
|
88
87
|
"post:query": "query",
|
|
89
|
-
"post:update": "updateWhere",
|
|
88
|
+
"post:bulk-update": "updateWhere",
|
|
90
89
|
"post:aggregate": "aggregate"
|
|
91
90
|
};
|
|
92
91
|
const COMPONENT_RESPONSE_STATUS = {
|
|
@@ -103,7 +102,7 @@ function isObject$2(value) {
|
|
|
103
102
|
function recordOperation(path, method) {
|
|
104
103
|
const match = RECORD_ROUTE.exec(path);
|
|
105
104
|
const objectCode = match?.[1];
|
|
106
|
-
if (objectCode === void 0 || objectCode === "
|
|
105
|
+
if (objectCode === void 0 || objectCode === "objects") return void 0;
|
|
107
106
|
const key = `${method.toLowerCase()}:${match?.at(2) ?? ""}`;
|
|
108
107
|
if (!Object.hasOwn(VERBS, key)) return void 0;
|
|
109
108
|
return {
|
|
@@ -113,7 +112,7 @@ function recordOperation(path, method) {
|
|
|
113
112
|
}
|
|
114
113
|
function recordObjectCode(path) {
|
|
115
114
|
const match = RECORD_ROUTE.exec(path);
|
|
116
|
-
return match?.[1] === "
|
|
115
|
+
return match?.[1] === "objects" ? void 0 : match?.[1];
|
|
117
116
|
}
|
|
118
117
|
function withRecordOperationId(path, method, operation) {
|
|
119
118
|
const record = recordOperation(path, method);
|
|
@@ -130,13 +129,13 @@ function batchEntry(entry, index) {
|
|
|
130
129
|
const enumPath = Array.isArray(path?.enum) && path.enum.length === 1 ? path.enum[0] : void 0;
|
|
131
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)}`);
|
|
132
131
|
if (methodValue === "POST" && typeof enumPath === "string") {
|
|
133
|
-
const updateWhere = /^\/v1\/([^/]+)\/update$/.exec(enumPath);
|
|
132
|
+
const updateWhere = /^\/v1\/([^/]+)\/bulk-update$/.exec(enumPath);
|
|
134
133
|
if (updateWhere?.[1] !== void 0) return {
|
|
135
134
|
objectCode: updateWhere[1],
|
|
136
135
|
verb: "updateWhere"
|
|
137
136
|
};
|
|
138
137
|
const create = /^\/v1\/([^/]+)$/.exec(enumPath);
|
|
139
|
-
if (create?.[1] !== void 0
|
|
138
|
+
if (create?.[1] !== void 0) return {
|
|
140
139
|
objectCode: create[1],
|
|
141
140
|
verb: "create"
|
|
142
141
|
};
|
|
@@ -225,7 +224,7 @@ function prepareRecordSpec(spec, manifest) {
|
|
|
225
224
|
}));
|
|
226
225
|
manifest?.objects.forEach(({ code, optionalAttributes }) => {
|
|
227
226
|
if (optionalAttributes === void 0 || optionalAttributes.length === 0) return;
|
|
228
|
-
const responsePath = `/v1/${code}/{
|
|
227
|
+
const responsePath = `/v1/${code}/{record}`;
|
|
229
228
|
const response = prepared.paths[responsePath]?.get?.responses;
|
|
230
229
|
const schema = isObject$2(response) && isObject$2(response["200"]) ? response["200"].content : void 0;
|
|
231
230
|
const mediaType = isObject$2(schema) && isObject$2(schema["application/json"]) ? schema["application/json"] : void 0;
|
|
@@ -263,8 +262,7 @@ function describeValue$1(value, expectedRef = false) {
|
|
|
263
262
|
if (typeof value === "object") return expectedRef ? "an object without a valid $ref" : "an object";
|
|
264
263
|
return `${typeof value} ${JSON.stringify(value)}`;
|
|
265
264
|
}
|
|
266
|
-
|
|
267
|
-
//#region src/typegen/catalog.ts
|
|
265
|
+
// src/typegen/catalog.ts
|
|
268
266
|
const COMPONENT_REF_PREFIX = "#/components/schemas/";
|
|
269
267
|
const RESERVED_ATTRIBUTE_CODES = /* @__PURE__ */ new Set([
|
|
270
268
|
"id",
|
|
@@ -310,8 +308,7 @@ function catalogFromSpec(spec) {
|
|
|
310
308
|
return [[operation.objectCode, Object.keys(schema.properties).filter((code) => !RESERVED_ATTRIBUTE_CODES.has(code))]];
|
|
311
309
|
}));
|
|
312
310
|
}
|
|
313
|
-
|
|
314
|
-
//#region src/typegen/guard.ts
|
|
311
|
+
// src/typegen/guard.ts
|
|
315
312
|
function isObject(value) {
|
|
316
313
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
317
314
|
}
|
|
@@ -326,8 +323,7 @@ function guardManifestForSpec(spec, manifest) {
|
|
|
326
323
|
const missing = manifest.objects.find(({ code }) => !available.includes(code));
|
|
327
324
|
if (missing !== void 0) throw new Error(`object "${missing.code}" not found in org spec (available: ${available.join(", ")})`);
|
|
328
325
|
}
|
|
329
|
-
|
|
330
|
-
//#region src/typegen/register.ts
|
|
326
|
+
// src/typegen/register.ts
|
|
331
327
|
function registerFile(catalog, options, publicApplet) {
|
|
332
328
|
const catalogType = catalog === void 0 ? "type Catalog = Record<string, string>;" : `type Catalog = {
|
|
333
329
|
${Object.entries(catalog).map(([objectCode, attributeCodes]) => ` ${JSON.stringify(objectCode)}: ${attributeCodes.map((code) => JSON.stringify(code)).join(" | ") || "never"};`).join("\n")}
|
|
@@ -391,8 +387,7 @@ async function writeRegisterDeclaration(outDir, catalog, publicApplet = false) {
|
|
|
391
387
|
hasServer: existsSync(resolve(outDir, "../src/server/main.ts"))
|
|
392
388
|
}, publicApplet));
|
|
393
389
|
}
|
|
394
|
-
|
|
395
|
-
//#region src/typegen/generate.ts
|
|
390
|
+
// src/typegen/generate.ts
|
|
396
391
|
const execFileAsync = promisify(execFile);
|
|
397
392
|
const require = createRequire(import.meta.url);
|
|
398
393
|
function routesModuleSource(routes) {
|
|
@@ -515,7 +510,7 @@ async function writeContractFiles(outDir, manifest, spec) {
|
|
|
515
510
|
await generateContract(preparedSpec, outDir);
|
|
516
511
|
await writeManifestGen(outDir, manifest);
|
|
517
512
|
await writeRegisterDeclaration(outDir, catalog, manifest.public === true);
|
|
518
|
-
const hasBatch = Object.hasOwn(preparedSpec.paths, "/v1/batch");
|
|
513
|
+
const hasBatch = Object.hasOwn(preparedSpec.paths, "/v1/batch/run");
|
|
519
514
|
await writeFile(resolve(outDir, "index.ts"), `// Generated by applet generate — do not edit.
|
|
520
515
|
import { emailContract, filesContract as files, meContract as me, pruneContract, registerHitchContract, teamsContract as teams, telephonyContract as telephony } from "@hitch42/applet";
|
|
521
516
|
import manifest from "./manifest.gen.ts";
|
|
@@ -545,11 +540,11 @@ async function syncContract(outDir, manifest) {
|
|
|
545
540
|
}
|
|
546
541
|
async function runGenerate() {
|
|
547
542
|
await prepareAppletProject();
|
|
548
|
-
const root = process.cwd();
|
|
549
|
-
const outDir = resolve("
|
|
543
|
+
const root = findAppletRoot(process.cwd()) ?? process.cwd();
|
|
544
|
+
const outDir = resolve(root, ".hitch");
|
|
550
545
|
const manifest = await loadAppletConfig(process.cwd());
|
|
551
546
|
const { spec, theme: themeStylesheet } = await loadBuildInputs(manifest);
|
|
552
|
-
const stagedDir = await mkdtemp(resolve("
|
|
547
|
+
const stagedDir = await mkdtemp(resolve(root, ".hitch.tmp-"));
|
|
553
548
|
const backupDir = `${stagedDir}.backup`;
|
|
554
549
|
let staged = true;
|
|
555
550
|
let backedUp = false;
|
|
@@ -595,5 +590,4 @@ async function runGenerate() {
|
|
|
595
590
|
if (backedUp && !await fileExists(outDir)) await rename(backupDir, outDir);
|
|
596
591
|
}
|
|
597
592
|
}
|
|
598
|
-
//#endregion
|
|
599
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 };
|