@newtype-ai/nit 0.1.0
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/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/chunk-5EGCFUZ7.js +927 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +212 -0
- package/dist/index.d.ts +243 -0
- package/dist/index.js +39 -0
- package/package.json +47 -0
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
// nit — version control for agent cards
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { promises as fs6, statSync } from "fs";
|
|
5
|
+
import { join as join6, basename as basename2, resolve } from "path";
|
|
6
|
+
|
|
7
|
+
// src/objects.ts
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import { promises as fs } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
function hashObject(type, content) {
|
|
12
|
+
const buf = Buffer.from(content, "utf-8");
|
|
13
|
+
const header = `${type} ${buf.byteLength}\0`;
|
|
14
|
+
const hash = createHash("sha256");
|
|
15
|
+
hash.update(header);
|
|
16
|
+
hash.update(buf);
|
|
17
|
+
return hash.digest("hex");
|
|
18
|
+
}
|
|
19
|
+
async function writeObject(nitDir, type, content) {
|
|
20
|
+
const hex = hashObject(type, content);
|
|
21
|
+
const dir = join(nitDir, "objects", hex.slice(0, 2));
|
|
22
|
+
const file = join(dir, hex.slice(2));
|
|
23
|
+
try {
|
|
24
|
+
await fs.access(file);
|
|
25
|
+
return hex;
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
await fs.mkdir(dir, { recursive: true });
|
|
29
|
+
await fs.writeFile(file, content, "utf-8");
|
|
30
|
+
return hex;
|
|
31
|
+
}
|
|
32
|
+
async function readObject(nitDir, hash) {
|
|
33
|
+
const file = join(nitDir, "objects", hash.slice(0, 2), hash.slice(2));
|
|
34
|
+
try {
|
|
35
|
+
return await fs.readFile(file, "utf-8");
|
|
36
|
+
} catch {
|
|
37
|
+
throw new Error(`Object not found: ${hash}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function serializeCommit(commit2) {
|
|
41
|
+
const lines = [];
|
|
42
|
+
lines.push(`card ${commit2.card}`);
|
|
43
|
+
if (commit2.parent !== null) {
|
|
44
|
+
lines.push(`parent ${commit2.parent}`);
|
|
45
|
+
}
|
|
46
|
+
lines.push(`author ${commit2.author} ${commit2.timestamp}`);
|
|
47
|
+
lines.push("");
|
|
48
|
+
lines.push(commit2.message);
|
|
49
|
+
return lines.join("\n");
|
|
50
|
+
}
|
|
51
|
+
function parseCommit(hash, raw) {
|
|
52
|
+
const lines = raw.split("\n");
|
|
53
|
+
let card = "";
|
|
54
|
+
let parent = null;
|
|
55
|
+
let author = "";
|
|
56
|
+
let timestamp = 0;
|
|
57
|
+
let messageStart = -1;
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
const line = lines[i];
|
|
60
|
+
if (line === "") {
|
|
61
|
+
messageStart = i + 1;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (line.startsWith("card ")) {
|
|
65
|
+
card = line.slice(5);
|
|
66
|
+
} else if (line.startsWith("parent ")) {
|
|
67
|
+
parent = line.slice(7);
|
|
68
|
+
} else if (line.startsWith("author ")) {
|
|
69
|
+
const authorPart = line.slice(7);
|
|
70
|
+
const lastSpace = authorPart.lastIndexOf(" ");
|
|
71
|
+
author = authorPart.slice(0, lastSpace);
|
|
72
|
+
timestamp = parseInt(authorPart.slice(lastSpace + 1), 10);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!card) {
|
|
76
|
+
throw new Error(`Malformed commit object ${hash}: missing card hash`);
|
|
77
|
+
}
|
|
78
|
+
const message = messageStart >= 0 ? lines.slice(messageStart).join("\n") : "";
|
|
79
|
+
return {
|
|
80
|
+
type: "commit",
|
|
81
|
+
hash,
|
|
82
|
+
card,
|
|
83
|
+
parent,
|
|
84
|
+
author,
|
|
85
|
+
timestamp,
|
|
86
|
+
message
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/refs.ts
|
|
91
|
+
import { promises as fs2 } from "fs";
|
|
92
|
+
import { join as join2 } from "path";
|
|
93
|
+
async function getHead(nitDir) {
|
|
94
|
+
const headPath = join2(nitDir, "HEAD");
|
|
95
|
+
const content = (await fs2.readFile(headPath, "utf-8")).trim();
|
|
96
|
+
if (!content.startsWith("ref: ")) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`HEAD is in an unexpected state: "${content}". Only symbolic refs are supported.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return { type: "ref", ref: content.slice(5) };
|
|
102
|
+
}
|
|
103
|
+
async function resolveHead(nitDir) {
|
|
104
|
+
const head = await getHead(nitDir);
|
|
105
|
+
const refPath = join2(nitDir, head.ref);
|
|
106
|
+
try {
|
|
107
|
+
return (await fs2.readFile(refPath, "utf-8")).trim();
|
|
108
|
+
} catch {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Branch ref ${head.ref} does not exist. Repository may be empty.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function getCurrentBranch(nitDir) {
|
|
115
|
+
const head = await getHead(nitDir);
|
|
116
|
+
const prefix = "refs/heads/";
|
|
117
|
+
if (!head.ref.startsWith(prefix)) {
|
|
118
|
+
throw new Error(`HEAD ref has unexpected format: ${head.ref}`);
|
|
119
|
+
}
|
|
120
|
+
return head.ref.slice(prefix.length);
|
|
121
|
+
}
|
|
122
|
+
async function setBranch(nitDir, branch2, commitHash) {
|
|
123
|
+
const refPath = join2(nitDir, "refs", "heads", branch2);
|
|
124
|
+
await fs2.mkdir(join2(nitDir, "refs", "heads"), { recursive: true });
|
|
125
|
+
await fs2.writeFile(refPath, commitHash + "\n", "utf-8");
|
|
126
|
+
}
|
|
127
|
+
async function getBranch(nitDir, branch2) {
|
|
128
|
+
const refPath = join2(nitDir, "refs", "heads", branch2);
|
|
129
|
+
try {
|
|
130
|
+
return (await fs2.readFile(refPath, "utf-8")).trim();
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function listBranches(nitDir) {
|
|
136
|
+
const headsDir = join2(nitDir, "refs", "heads");
|
|
137
|
+
const branches = [];
|
|
138
|
+
try {
|
|
139
|
+
const entries = await fs2.readdir(headsDir);
|
|
140
|
+
for (const name of entries) {
|
|
141
|
+
const refPath = join2(headsDir, name);
|
|
142
|
+
const stat = await fs2.stat(refPath);
|
|
143
|
+
if (stat.isFile()) {
|
|
144
|
+
const commitHash = (await fs2.readFile(refPath, "utf-8")).trim();
|
|
145
|
+
branches.push({ name, commitHash });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
return branches.sort((a, b) => a.name.localeCompare(b.name));
|
|
151
|
+
}
|
|
152
|
+
async function setHead(nitDir, branch2) {
|
|
153
|
+
const headPath = join2(nitDir, "HEAD");
|
|
154
|
+
await fs2.writeFile(headPath, `ref: refs/heads/${branch2}
|
|
155
|
+
`, "utf-8");
|
|
156
|
+
}
|
|
157
|
+
async function setRemoteRef(nitDir, remote2, branch2, commitHash) {
|
|
158
|
+
const dir = join2(nitDir, "refs", "remote", remote2);
|
|
159
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
160
|
+
await fs2.writeFile(join2(dir, branch2), commitHash + "\n", "utf-8");
|
|
161
|
+
}
|
|
162
|
+
async function getRemoteRef(nitDir, remote2, branch2) {
|
|
163
|
+
const refPath = join2(nitDir, "refs", "remote", remote2, branch2);
|
|
164
|
+
try {
|
|
165
|
+
return (await fs2.readFile(refPath, "utf-8")).trim();
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/identity.ts
|
|
172
|
+
import {
|
|
173
|
+
generateKeyPairSync,
|
|
174
|
+
createPrivateKey,
|
|
175
|
+
createPublicKey,
|
|
176
|
+
sign,
|
|
177
|
+
verify
|
|
178
|
+
} from "crypto";
|
|
179
|
+
import { promises as fs3 } from "fs";
|
|
180
|
+
import { join as join3 } from "path";
|
|
181
|
+
function base64urlToBase64(b64url) {
|
|
182
|
+
let s = b64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
183
|
+
while (s.length % 4 !== 0) s += "=";
|
|
184
|
+
return s;
|
|
185
|
+
}
|
|
186
|
+
function base64ToBase64url(b64) {
|
|
187
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
188
|
+
}
|
|
189
|
+
async function generateKeypair(nitDir) {
|
|
190
|
+
const identityDir = join3(nitDir, "identity");
|
|
191
|
+
await fs3.mkdir(identityDir, { recursive: true });
|
|
192
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
193
|
+
const pubJwk = publicKey.export({ format: "jwk" });
|
|
194
|
+
const privJwk = privateKey.export({ format: "jwk" });
|
|
195
|
+
const pubBase64 = base64urlToBase64(pubJwk.x);
|
|
196
|
+
const privBase64 = base64urlToBase64(privJwk.d);
|
|
197
|
+
const pubPath = join3(identityDir, "agent.pub");
|
|
198
|
+
const keyPath = join3(identityDir, "agent.key");
|
|
199
|
+
await fs3.writeFile(pubPath, pubBase64 + "\n", "utf-8");
|
|
200
|
+
await fs3.writeFile(keyPath, privBase64 + "\n", {
|
|
201
|
+
mode: 384,
|
|
202
|
+
encoding: "utf-8"
|
|
203
|
+
});
|
|
204
|
+
return { publicKey: pubBase64, privateKey: privBase64 };
|
|
205
|
+
}
|
|
206
|
+
async function loadPublicKey(nitDir) {
|
|
207
|
+
const pubPath = join3(nitDir, "identity", "agent.pub");
|
|
208
|
+
try {
|
|
209
|
+
return (await fs3.readFile(pubPath, "utf-8")).trim();
|
|
210
|
+
} catch {
|
|
211
|
+
throw new Error(
|
|
212
|
+
"No identity found. Run `nit init` to generate a keypair."
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function loadPrivateKey(nitDir) {
|
|
217
|
+
const pubBase64 = await loadPublicKey(nitDir);
|
|
218
|
+
const keyPath = join3(nitDir, "identity", "agent.key");
|
|
219
|
+
let privBase64;
|
|
220
|
+
try {
|
|
221
|
+
privBase64 = (await fs3.readFile(keyPath, "utf-8")).trim();
|
|
222
|
+
} catch {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"Private key not found at .nit/identity/agent.key. Regenerate with `nit init`."
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const xB64url = base64ToBase64url(pubBase64);
|
|
228
|
+
const dB64url = base64ToBase64url(privBase64);
|
|
229
|
+
return createPrivateKey({
|
|
230
|
+
key: { kty: "OKP", crv: "Ed25519", x: xB64url, d: dB64url },
|
|
231
|
+
format: "jwk"
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
function formatPublicKeyField(pubBase64) {
|
|
235
|
+
return `ed25519:${pubBase64}`;
|
|
236
|
+
}
|
|
237
|
+
function parsePublicKeyField(field) {
|
|
238
|
+
const prefix = "ed25519:";
|
|
239
|
+
if (!field.startsWith(prefix)) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`Invalid publicKey format: expected "ed25519:<base64>", got "${field}"`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return field.slice(prefix.length);
|
|
245
|
+
}
|
|
246
|
+
async function signChallenge(nitDir, challenge) {
|
|
247
|
+
const privateKey = await loadPrivateKey(nitDir);
|
|
248
|
+
const sig = sign(null, Buffer.from(challenge, "utf-8"), privateKey);
|
|
249
|
+
return sig.toString("base64");
|
|
250
|
+
}
|
|
251
|
+
function verifySignature(pubBase64, challenge, signatureBase64) {
|
|
252
|
+
const xB64url = base64ToBase64url(pubBase64);
|
|
253
|
+
const publicKeyObj = createPublicKey({
|
|
254
|
+
key: { kty: "OKP", crv: "Ed25519", x: xB64url },
|
|
255
|
+
format: "jwk"
|
|
256
|
+
});
|
|
257
|
+
return verify(
|
|
258
|
+
null,
|
|
259
|
+
Buffer.from(challenge, "utf-8"),
|
|
260
|
+
publicKeyObj,
|
|
261
|
+
Buffer.from(signatureBase64, "base64")
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/config.ts
|
|
266
|
+
import { promises as fs4 } from "fs";
|
|
267
|
+
import { join as join4 } from "path";
|
|
268
|
+
var CONFIG_FILE = "config";
|
|
269
|
+
async function readConfig(nitDir) {
|
|
270
|
+
const configPath = join4(nitDir, CONFIG_FILE);
|
|
271
|
+
let raw;
|
|
272
|
+
try {
|
|
273
|
+
raw = await fs4.readFile(configPath, "utf-8");
|
|
274
|
+
} catch {
|
|
275
|
+
return { remotes: {} };
|
|
276
|
+
}
|
|
277
|
+
return parseConfig(raw);
|
|
278
|
+
}
|
|
279
|
+
async function getRemoteCredential(nitDir, remoteName) {
|
|
280
|
+
const config = await readConfig(nitDir);
|
|
281
|
+
return config.remotes[remoteName]?.credential ?? null;
|
|
282
|
+
}
|
|
283
|
+
function parseConfig(raw) {
|
|
284
|
+
const remotes = {};
|
|
285
|
+
let currentRemote = null;
|
|
286
|
+
for (const line of raw.split("\n")) {
|
|
287
|
+
const trimmed = line.trim();
|
|
288
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
289
|
+
const sectionMatch = trimmed.match(/^\[remote\s+"([^"]+)"\]$/);
|
|
290
|
+
if (sectionMatch) {
|
|
291
|
+
currentRemote = sectionMatch[1];
|
|
292
|
+
if (!remotes[currentRemote]) {
|
|
293
|
+
remotes[currentRemote] = {};
|
|
294
|
+
}
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (currentRemote !== null) {
|
|
298
|
+
const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
299
|
+
if (kvMatch) {
|
|
300
|
+
const [, key, value] = kvMatch;
|
|
301
|
+
if (key === "credential") {
|
|
302
|
+
remotes[currentRemote].credential = value.trim();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return { remotes };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/skills.ts
|
|
311
|
+
import { promises as fs5 } from "fs";
|
|
312
|
+
import { join as join5 } from "path";
|
|
313
|
+
import { homedir } from "os";
|
|
314
|
+
async function discoverSkills(projectDir2) {
|
|
315
|
+
const home = homedir();
|
|
316
|
+
const searchDirs = [
|
|
317
|
+
// Project-local (all known agent frameworks)
|
|
318
|
+
join5(projectDir2, ".claude", "skills"),
|
|
319
|
+
join5(projectDir2, ".cursor", "skills"),
|
|
320
|
+
join5(projectDir2, ".windsurf", "skills"),
|
|
321
|
+
join5(projectDir2, ".codex", "skills"),
|
|
322
|
+
join5(projectDir2, ".agents", "skills"),
|
|
323
|
+
// User-global
|
|
324
|
+
join5(home, ".claude", "skills"),
|
|
325
|
+
// Claude Code + Cursor (shared)
|
|
326
|
+
join5(home, ".codex", "skills"),
|
|
327
|
+
// Codex CLI
|
|
328
|
+
join5(home, ".codeium", "windsurf", "skills")
|
|
329
|
+
// Windsurf
|
|
330
|
+
];
|
|
331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
332
|
+
const skills = [];
|
|
333
|
+
for (const searchDir of searchDirs) {
|
|
334
|
+
const found = await scanSkillDir(searchDir);
|
|
335
|
+
for (const skill of found) {
|
|
336
|
+
if (!seen.has(skill.id)) {
|
|
337
|
+
seen.add(skill.id);
|
|
338
|
+
skills.push(skill);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return skills;
|
|
343
|
+
}
|
|
344
|
+
async function resolveSkillPointers(card, projectDir2) {
|
|
345
|
+
const discovered = await discoverSkills(projectDir2);
|
|
346
|
+
const skillMap = new Map(discovered.map((s) => [s.id, s]));
|
|
347
|
+
const resolvedSkills = card.skills.map((skill) => {
|
|
348
|
+
const meta = skillMap.get(skill.id);
|
|
349
|
+
if (!meta) return skill;
|
|
350
|
+
return {
|
|
351
|
+
...skill,
|
|
352
|
+
name: meta.name,
|
|
353
|
+
description: meta.description
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
return { ...card, skills: resolvedSkills };
|
|
357
|
+
}
|
|
358
|
+
async function scanSkillDir(dir) {
|
|
359
|
+
let entries;
|
|
360
|
+
try {
|
|
361
|
+
entries = await fs5.readdir(dir);
|
|
362
|
+
} catch {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
const skills = [];
|
|
366
|
+
for (const entry of entries) {
|
|
367
|
+
const skillMdPath = join5(dir, entry, "SKILL.md");
|
|
368
|
+
try {
|
|
369
|
+
const content = await fs5.readFile(skillMdPath, "utf-8");
|
|
370
|
+
const meta = parseFrontmatter(content, entry, skillMdPath);
|
|
371
|
+
if (meta) skills.push(meta);
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return skills;
|
|
376
|
+
}
|
|
377
|
+
function parseFrontmatter(content, dirName, filePath) {
|
|
378
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
379
|
+
if (!fmMatch) return null;
|
|
380
|
+
const fmBlock = fmMatch[1];
|
|
381
|
+
const fields = {};
|
|
382
|
+
let inMetadata = false;
|
|
383
|
+
let metadataVersion;
|
|
384
|
+
for (const line of fmBlock.split("\n")) {
|
|
385
|
+
const trimmed = line.trimEnd();
|
|
386
|
+
if (trimmed === "metadata:") {
|
|
387
|
+
inMetadata = true;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (inMetadata) {
|
|
391
|
+
const nestedMatch = trimmed.match(/^\s+(\w+):\s*(.+)$/);
|
|
392
|
+
if (nestedMatch) {
|
|
393
|
+
if (nestedMatch[1] === "version") {
|
|
394
|
+
metadataVersion = nestedMatch[2].trim();
|
|
395
|
+
}
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
inMetadata = false;
|
|
399
|
+
}
|
|
400
|
+
const kvMatch = trimmed.match(/^(\w+):\s*(.+)$/);
|
|
401
|
+
if (kvMatch) {
|
|
402
|
+
fields[kvMatch[1]] = kvMatch[2].trim();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const name = fields["name"];
|
|
406
|
+
const description = fields["description"];
|
|
407
|
+
if (!name || !description) return null;
|
|
408
|
+
return {
|
|
409
|
+
id: dirName,
|
|
410
|
+
name,
|
|
411
|
+
description,
|
|
412
|
+
version: metadataVersion,
|
|
413
|
+
path: filePath
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/diff.ts
|
|
418
|
+
function diffCards(oldCard, newCard) {
|
|
419
|
+
const fields = [];
|
|
420
|
+
const scalarFields = [
|
|
421
|
+
"protocolVersion",
|
|
422
|
+
"name",
|
|
423
|
+
"description",
|
|
424
|
+
"version",
|
|
425
|
+
"url",
|
|
426
|
+
"publicKey",
|
|
427
|
+
"iconUrl",
|
|
428
|
+
"documentationUrl"
|
|
429
|
+
];
|
|
430
|
+
for (const field of scalarFields) {
|
|
431
|
+
const oldVal = oldCard[field];
|
|
432
|
+
const newVal = newCard[field];
|
|
433
|
+
if (oldVal !== newVal) {
|
|
434
|
+
fields.push({ field, old: oldVal, new: newVal });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const oldProvider = JSON.stringify(oldCard.provider ?? null);
|
|
438
|
+
const newProvider = JSON.stringify(newCard.provider ?? null);
|
|
439
|
+
if (oldProvider !== newProvider) {
|
|
440
|
+
fields.push({ field: "provider", old: oldCard.provider, new: newCard.provider });
|
|
441
|
+
}
|
|
442
|
+
const arrayFields = [
|
|
443
|
+
"defaultInputModes",
|
|
444
|
+
"defaultOutputModes"
|
|
445
|
+
];
|
|
446
|
+
for (const field of arrayFields) {
|
|
447
|
+
const oldArr = JSON.stringify(oldCard[field]);
|
|
448
|
+
const newArr = JSON.stringify(newCard[field]);
|
|
449
|
+
if (oldArr !== newArr) {
|
|
450
|
+
fields.push({ field, old: oldCard[field], new: newCard[field] });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const oldSkillMap = new Map(oldCard.skills.map((s) => [s.id, s]));
|
|
454
|
+
const newSkillMap = new Map(newCard.skills.map((s) => [s.id, s]));
|
|
455
|
+
const skillsAdded = [];
|
|
456
|
+
const skillsRemoved = [];
|
|
457
|
+
const skillsModified = [];
|
|
458
|
+
for (const [id] of newSkillMap) {
|
|
459
|
+
if (!oldSkillMap.has(id)) {
|
|
460
|
+
skillsAdded.push(id);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
for (const [id] of oldSkillMap) {
|
|
464
|
+
if (!newSkillMap.has(id)) {
|
|
465
|
+
skillsRemoved.push(id);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
for (const [id, newSkill] of newSkillMap) {
|
|
469
|
+
const oldSkill = oldSkillMap.get(id);
|
|
470
|
+
if (oldSkill && !skillsEqual(oldSkill, newSkill)) {
|
|
471
|
+
skillsModified.push(id);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const changed = fields.length > 0 || skillsAdded.length > 0 || skillsRemoved.length > 0 || skillsModified.length > 0;
|
|
475
|
+
return { changed, fields, skillsAdded, skillsRemoved, skillsModified };
|
|
476
|
+
}
|
|
477
|
+
function formatDiff(diff2) {
|
|
478
|
+
if (!diff2.changed) {
|
|
479
|
+
return "No changes.";
|
|
480
|
+
}
|
|
481
|
+
const lines = [];
|
|
482
|
+
for (const fd of diff2.fields) {
|
|
483
|
+
const oldStr = formatValue(fd.old);
|
|
484
|
+
const newStr = formatValue(fd.new);
|
|
485
|
+
lines.push(` ${fd.field}:`);
|
|
486
|
+
lines.push(`\x1B[31m - ${oldStr}\x1B[0m`);
|
|
487
|
+
lines.push(`\x1B[32m + ${newStr}\x1B[0m`);
|
|
488
|
+
}
|
|
489
|
+
for (const id of diff2.skillsAdded) {
|
|
490
|
+
lines.push(`\x1B[32m + skill: ${id}\x1B[0m`);
|
|
491
|
+
}
|
|
492
|
+
for (const id of diff2.skillsRemoved) {
|
|
493
|
+
lines.push(`\x1B[31m - skill: ${id}\x1B[0m`);
|
|
494
|
+
}
|
|
495
|
+
for (const id of diff2.skillsModified) {
|
|
496
|
+
lines.push(`\x1B[33m ~ skill: ${id} (modified)\x1B[0m`);
|
|
497
|
+
}
|
|
498
|
+
return lines.join("\n");
|
|
499
|
+
}
|
|
500
|
+
function skillsEqual(a, b) {
|
|
501
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
502
|
+
}
|
|
503
|
+
function formatValue(val) {
|
|
504
|
+
if (val === void 0) return "(unset)";
|
|
505
|
+
if (val === null) return "(null)";
|
|
506
|
+
if (typeof val === "string") return val;
|
|
507
|
+
return JSON.stringify(val);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/remote.ts
|
|
511
|
+
var API_BASE = "https://api.newtype-ai.org";
|
|
512
|
+
async function pushBranch(nitDir, remoteName, branch2, cardJson, commitHash) {
|
|
513
|
+
const credential = await getRemoteCredential(nitDir, remoteName);
|
|
514
|
+
if (!credential) {
|
|
515
|
+
return {
|
|
516
|
+
branch: branch2,
|
|
517
|
+
commitHash,
|
|
518
|
+
remoteUrl: API_BASE,
|
|
519
|
+
success: false,
|
|
520
|
+
error: `No credential configured for remote "${remoteName}". Run: nit remote`
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
const url = `${API_BASE}/agent-card/branches/${encodeURIComponent(branch2)}`;
|
|
524
|
+
try {
|
|
525
|
+
const res = await fetch(url, {
|
|
526
|
+
method: "PUT",
|
|
527
|
+
headers: {
|
|
528
|
+
"Content-Type": "application/json",
|
|
529
|
+
Authorization: `Bearer ${credential}`
|
|
530
|
+
},
|
|
531
|
+
body: JSON.stringify({ card_json: cardJson, commit_hash: commitHash })
|
|
532
|
+
});
|
|
533
|
+
if (!res.ok) {
|
|
534
|
+
const body = await res.text();
|
|
535
|
+
return {
|
|
536
|
+
branch: branch2,
|
|
537
|
+
commitHash,
|
|
538
|
+
remoteUrl: API_BASE,
|
|
539
|
+
success: false,
|
|
540
|
+
error: `HTTP ${res.status}: ${body}`
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
return { branch: branch2, commitHash, remoteUrl: API_BASE, success: true };
|
|
544
|
+
} catch (err) {
|
|
545
|
+
return {
|
|
546
|
+
branch: branch2,
|
|
547
|
+
commitHash,
|
|
548
|
+
remoteUrl: API_BASE,
|
|
549
|
+
success: false,
|
|
550
|
+
error: err instanceof Error ? err.message : String(err)
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async function fetchBranchCard(cardUrl, branch2, nitDir) {
|
|
555
|
+
const baseUrl = cardUrl.replace(/\/$/, "");
|
|
556
|
+
let url = `${baseUrl}/.well-known/agent-card.json`;
|
|
557
|
+
if (branch2 !== "main") {
|
|
558
|
+
url += `?branch=${encodeURIComponent(branch2)}`;
|
|
559
|
+
}
|
|
560
|
+
const res = await fetch(url);
|
|
561
|
+
if (res.ok) {
|
|
562
|
+
return await res.json();
|
|
563
|
+
}
|
|
564
|
+
if (res.status === 401 && branch2 !== "main") {
|
|
565
|
+
if (!nitDir) {
|
|
566
|
+
throw new Error(
|
|
567
|
+
`Branch "${branch2}" requires authentication. Provide nitDir for signing.`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
const challengeData = await res.json();
|
|
571
|
+
const signature = await signChallenge(nitDir, challengeData.challenge);
|
|
572
|
+
const authRes = await fetch(url, {
|
|
573
|
+
headers: {
|
|
574
|
+
"X-Nit-Challenge": challengeData.challenge,
|
|
575
|
+
"X-Nit-Signature": signature
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
if (!authRes.ok) {
|
|
579
|
+
const body = await authRes.text();
|
|
580
|
+
throw new Error(
|
|
581
|
+
`Failed to fetch branch "${branch2}" after challenge: HTTP ${authRes.status} ${body}`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
return await authRes.json();
|
|
585
|
+
}
|
|
586
|
+
throw new Error(`Failed to fetch card: HTTP ${res.status}`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/index.ts
|
|
590
|
+
var NIT_DIR = ".nit";
|
|
591
|
+
var CARD_FILE = "agent-card.json";
|
|
592
|
+
function findNitDir(startDir) {
|
|
593
|
+
let dir = resolve(startDir || process.cwd());
|
|
594
|
+
while (true) {
|
|
595
|
+
const candidate = join6(dir, NIT_DIR);
|
|
596
|
+
try {
|
|
597
|
+
const s = statSync(candidate);
|
|
598
|
+
if (s.isDirectory()) return candidate;
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
const parent = resolve(dir, "..");
|
|
602
|
+
if (parent === dir) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
"Not a nit repository (or any parent directory). Run `nit init` first."
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
dir = parent;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function projectDir(nitDir) {
|
|
611
|
+
return resolve(nitDir, "..");
|
|
612
|
+
}
|
|
613
|
+
async function readWorkingCard(nitDir) {
|
|
614
|
+
const cardPath = join6(projectDir(nitDir), CARD_FILE);
|
|
615
|
+
try {
|
|
616
|
+
const raw = await fs6.readFile(cardPath, "utf-8");
|
|
617
|
+
return JSON.parse(raw);
|
|
618
|
+
} catch {
|
|
619
|
+
throw new Error(`Cannot read ${CARD_FILE}. Does it exist?`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function writeWorkingCard(nitDir, card) {
|
|
623
|
+
const cardPath = join6(projectDir(nitDir), CARD_FILE);
|
|
624
|
+
await fs6.writeFile(cardPath, JSON.stringify(card, null, 2) + "\n", "utf-8");
|
|
625
|
+
}
|
|
626
|
+
async function getCardAtCommit(nitDir, commitHash) {
|
|
627
|
+
const commitRaw = await readObject(nitDir, commitHash);
|
|
628
|
+
const commit2 = parseCommit(commitHash, commitRaw);
|
|
629
|
+
const cardRaw = await readObject(nitDir, commit2.card);
|
|
630
|
+
return JSON.parse(cardRaw);
|
|
631
|
+
}
|
|
632
|
+
async function getAuthorName(nitDir) {
|
|
633
|
+
try {
|
|
634
|
+
const card = await readWorkingCard(nitDir);
|
|
635
|
+
return card.name || basename2(projectDir(nitDir));
|
|
636
|
+
} catch {
|
|
637
|
+
return basename2(projectDir(nitDir));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async function init(options) {
|
|
641
|
+
const projDir = resolve(options?.projectDir || process.cwd());
|
|
642
|
+
const nitDir = join6(projDir, NIT_DIR);
|
|
643
|
+
try {
|
|
644
|
+
await fs6.access(nitDir);
|
|
645
|
+
throw new Error("Already initialized. .nit/ directory exists.");
|
|
646
|
+
} catch (err) {
|
|
647
|
+
if (err instanceof Error && err.message.startsWith("Already")) throw err;
|
|
648
|
+
}
|
|
649
|
+
await fs6.mkdir(join6(nitDir, "objects"), { recursive: true });
|
|
650
|
+
await fs6.mkdir(join6(nitDir, "refs", "heads"), { recursive: true });
|
|
651
|
+
await fs6.mkdir(join6(nitDir, "refs", "remote"), { recursive: true });
|
|
652
|
+
await fs6.mkdir(join6(nitDir, "identity"), { recursive: true });
|
|
653
|
+
await fs6.mkdir(join6(nitDir, "logs"), { recursive: true });
|
|
654
|
+
const { publicKey: pubBase64 } = await generateKeypair(nitDir);
|
|
655
|
+
const publicKeyField = formatPublicKeyField(pubBase64);
|
|
656
|
+
const cardPath = join6(projDir, CARD_FILE);
|
|
657
|
+
let card;
|
|
658
|
+
let skillsFound = [];
|
|
659
|
+
try {
|
|
660
|
+
const raw = await fs6.readFile(cardPath, "utf-8");
|
|
661
|
+
card = JSON.parse(raw);
|
|
662
|
+
card.publicKey = publicKeyField;
|
|
663
|
+
skillsFound = card.skills.map((s) => s.id);
|
|
664
|
+
} catch {
|
|
665
|
+
const discovered = await discoverSkills(projDir);
|
|
666
|
+
skillsFound = discovered.map((s) => s.id);
|
|
667
|
+
card = {
|
|
668
|
+
protocolVersion: "0.3.0",
|
|
669
|
+
name: basename2(projDir),
|
|
670
|
+
description: `AI agent working in ${basename2(projDir)}`,
|
|
671
|
+
version: "1.0.0",
|
|
672
|
+
url: "",
|
|
673
|
+
publicKey: publicKeyField,
|
|
674
|
+
defaultInputModes: ["text/plain"],
|
|
675
|
+
defaultOutputModes: ["text/plain"],
|
|
676
|
+
skills: discovered.map((s) => ({
|
|
677
|
+
id: s.id,
|
|
678
|
+
name: s.name,
|
|
679
|
+
description: s.description
|
|
680
|
+
}))
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
await writeWorkingCard(nitDir, card);
|
|
684
|
+
const cardJson = JSON.stringify(card, null, 2);
|
|
685
|
+
const cardHash = await writeObject(nitDir, "card", cardJson);
|
|
686
|
+
const commitContent = serializeCommit({
|
|
687
|
+
card: cardHash,
|
|
688
|
+
parent: null,
|
|
689
|
+
author: card.name,
|
|
690
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
691
|
+
message: "Initial commit"
|
|
692
|
+
});
|
|
693
|
+
const commitHash = await writeObject(nitDir, "commit", commitContent);
|
|
694
|
+
await setBranch(nitDir, "main", commitHash);
|
|
695
|
+
await setHead(nitDir, "main");
|
|
696
|
+
await fs6.writeFile(join6(nitDir, "logs", "HEAD"), "", "utf-8");
|
|
697
|
+
await fs6.writeFile(join6(nitDir, "config"), "", "utf-8");
|
|
698
|
+
return {
|
|
699
|
+
publicKey: publicKeyField,
|
|
700
|
+
cardUrl: card.url || null,
|
|
701
|
+
skillsFound
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
async function status(options) {
|
|
705
|
+
const nitDir = findNitDir(options?.projectDir);
|
|
706
|
+
const currentBranch = await getCurrentBranch(nitDir);
|
|
707
|
+
const pubBase64 = await loadPublicKey(nitDir);
|
|
708
|
+
const publicKey = formatPublicKeyField(pubBase64);
|
|
709
|
+
let uncommittedChanges = null;
|
|
710
|
+
try {
|
|
711
|
+
const headHash = await resolveHead(nitDir);
|
|
712
|
+
const headCard = await getCardAtCommit(nitDir, headHash);
|
|
713
|
+
const workingCard = await readWorkingCard(nitDir);
|
|
714
|
+
const d = diffCards(headCard, workingCard);
|
|
715
|
+
if (d.changed) {
|
|
716
|
+
uncommittedChanges = d;
|
|
717
|
+
}
|
|
718
|
+
} catch {
|
|
719
|
+
}
|
|
720
|
+
const branches = await listBranches(nitDir);
|
|
721
|
+
const branchStatus = [];
|
|
722
|
+
for (const b of branches) {
|
|
723
|
+
const remoteHash = await getRemoteRef(nitDir, "origin", b.name);
|
|
724
|
+
let ahead = 0;
|
|
725
|
+
if (remoteHash && remoteHash !== b.commitHash) {
|
|
726
|
+
let hash = b.commitHash;
|
|
727
|
+
while (hash && hash !== remoteHash) {
|
|
728
|
+
ahead++;
|
|
729
|
+
try {
|
|
730
|
+
const raw = await readObject(nitDir, hash);
|
|
731
|
+
const c = parseCommit(hash, raw);
|
|
732
|
+
hash = c.parent;
|
|
733
|
+
} catch {
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
} else if (!remoteHash) {
|
|
738
|
+
let hash = b.commitHash;
|
|
739
|
+
while (hash) {
|
|
740
|
+
ahead++;
|
|
741
|
+
try {
|
|
742
|
+
const raw = await readObject(nitDir, hash);
|
|
743
|
+
const c = parseCommit(hash, raw);
|
|
744
|
+
hash = c.parent;
|
|
745
|
+
} catch {
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
branchStatus.push({ name: b.name, ahead, behind: 0 });
|
|
751
|
+
}
|
|
752
|
+
return {
|
|
753
|
+
branch: currentBranch,
|
|
754
|
+
publicKey,
|
|
755
|
+
uncommittedChanges,
|
|
756
|
+
branches: branchStatus
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
async function commit(message, options) {
|
|
760
|
+
const nitDir = findNitDir(options?.projectDir);
|
|
761
|
+
const projDir = projectDir(nitDir);
|
|
762
|
+
let card = await readWorkingCard(nitDir);
|
|
763
|
+
card = await resolveSkillPointers(card, projDir);
|
|
764
|
+
await writeWorkingCard(nitDir, card);
|
|
765
|
+
const cardJson = JSON.stringify(card, null, 2);
|
|
766
|
+
const cardHash = await writeObject(nitDir, "card", cardJson);
|
|
767
|
+
const currentBranch = await getCurrentBranch(nitDir);
|
|
768
|
+
const parentHash = await getBranch(nitDir, currentBranch);
|
|
769
|
+
if (parentHash) {
|
|
770
|
+
const parentRaw = await readObject(nitDir, parentHash);
|
|
771
|
+
const parentCommit = parseCommit(parentHash, parentRaw);
|
|
772
|
+
if (parentCommit.card === cardHash) {
|
|
773
|
+
throw new Error("Nothing to commit \u2014 agent card is unchanged.");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const author = await getAuthorName(nitDir);
|
|
777
|
+
const commitContent = serializeCommit({
|
|
778
|
+
card: cardHash,
|
|
779
|
+
parent: parentHash,
|
|
780
|
+
author,
|
|
781
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
782
|
+
message
|
|
783
|
+
});
|
|
784
|
+
const commitHash = await writeObject(nitDir, "commit", commitContent);
|
|
785
|
+
await setBranch(nitDir, currentBranch, commitHash);
|
|
786
|
+
return {
|
|
787
|
+
type: "commit",
|
|
788
|
+
hash: commitHash,
|
|
789
|
+
card: cardHash,
|
|
790
|
+
parent: parentHash,
|
|
791
|
+
author,
|
|
792
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
793
|
+
message
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
async function log(options) {
|
|
797
|
+
const nitDir = findNitDir(options?.projectDir);
|
|
798
|
+
const currentBranch = await getCurrentBranch(nitDir);
|
|
799
|
+
let hash = await getBranch(nitDir, currentBranch);
|
|
800
|
+
const commits = [];
|
|
801
|
+
const limit = options?.count ?? 50;
|
|
802
|
+
while (hash && commits.length < limit) {
|
|
803
|
+
const raw = await readObject(nitDir, hash);
|
|
804
|
+
const c = parseCommit(hash, raw);
|
|
805
|
+
commits.push(c);
|
|
806
|
+
hash = c.parent;
|
|
807
|
+
}
|
|
808
|
+
return commits;
|
|
809
|
+
}
|
|
810
|
+
async function diff(target, options) {
|
|
811
|
+
const nitDir = findNitDir(options?.projectDir);
|
|
812
|
+
if (!target) {
|
|
813
|
+
const headHash2 = await resolveHead(nitDir);
|
|
814
|
+
const headCard2 = await getCardAtCommit(nitDir, headHash2);
|
|
815
|
+
const workingCard = await readWorkingCard(nitDir);
|
|
816
|
+
return diffCards(headCard2, workingCard);
|
|
817
|
+
}
|
|
818
|
+
const targetBranchHash = await getBranch(nitDir, target);
|
|
819
|
+
const headHash = await resolveHead(nitDir);
|
|
820
|
+
const headCard = await getCardAtCommit(nitDir, headHash);
|
|
821
|
+
if (targetBranchHash) {
|
|
822
|
+
const targetCard = await getCardAtCommit(nitDir, targetBranchHash);
|
|
823
|
+
return diffCards(headCard, targetCard);
|
|
824
|
+
}
|
|
825
|
+
if (/^[0-9a-f]{64}$/.test(target)) {
|
|
826
|
+
const targetCard = await getCardAtCommit(nitDir, target);
|
|
827
|
+
return diffCards(headCard, targetCard);
|
|
828
|
+
}
|
|
829
|
+
throw new Error(
|
|
830
|
+
`Unknown target "${target}". Provide a branch name or commit hash.`
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
async function branch(name, options) {
|
|
834
|
+
const nitDir = findNitDir(options?.projectDir);
|
|
835
|
+
if (name) {
|
|
836
|
+
const existing = await getBranch(nitDir, name);
|
|
837
|
+
if (existing) {
|
|
838
|
+
throw new Error(`Branch "${name}" already exists.`);
|
|
839
|
+
}
|
|
840
|
+
const headHash = await resolveHead(nitDir);
|
|
841
|
+
await setBranch(nitDir, name, headHash);
|
|
842
|
+
}
|
|
843
|
+
return listBranches(nitDir);
|
|
844
|
+
}
|
|
845
|
+
async function checkout(branchName, options) {
|
|
846
|
+
const nitDir = findNitDir(options?.projectDir);
|
|
847
|
+
try {
|
|
848
|
+
const headHash = await resolveHead(nitDir);
|
|
849
|
+
const headCard = await getCardAtCommit(nitDir, headHash);
|
|
850
|
+
const workingCard = await readWorkingCard(nitDir);
|
|
851
|
+
const d = diffCards(headCard, workingCard);
|
|
852
|
+
if (d.changed) {
|
|
853
|
+
throw new Error(
|
|
854
|
+
"You have uncommitted changes. Commit or discard them before switching branches."
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
} catch (err) {
|
|
858
|
+
if (err instanceof Error && err.message.includes("uncommitted changes")) {
|
|
859
|
+
throw err;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const targetHash = await getBranch(nitDir, branchName);
|
|
863
|
+
if (!targetHash) {
|
|
864
|
+
throw new Error(`Branch "${branchName}" does not exist.`);
|
|
865
|
+
}
|
|
866
|
+
const targetCard = await getCardAtCommit(nitDir, targetHash);
|
|
867
|
+
await writeWorkingCard(nitDir, targetCard);
|
|
868
|
+
await setHead(nitDir, branchName);
|
|
869
|
+
}
|
|
870
|
+
async function push(options) {
|
|
871
|
+
const nitDir = findNitDir(options?.projectDir);
|
|
872
|
+
const remoteName = options?.remoteName || "origin";
|
|
873
|
+
const branches = await listBranches(nitDir);
|
|
874
|
+
const currentBranch = await getCurrentBranch(nitDir);
|
|
875
|
+
const toPush = options?.all ? branches : branches.filter((b) => b.name === currentBranch);
|
|
876
|
+
if (toPush.length === 0) {
|
|
877
|
+
throw new Error("No branches to push.");
|
|
878
|
+
}
|
|
879
|
+
const results = [];
|
|
880
|
+
for (const b of toPush) {
|
|
881
|
+
const commitRaw = await readObject(nitDir, b.commitHash);
|
|
882
|
+
const c = parseCommit(b.commitHash, commitRaw);
|
|
883
|
+
const cardJson = await readObject(nitDir, c.card);
|
|
884
|
+
const result = await pushBranch(
|
|
885
|
+
nitDir,
|
|
886
|
+
remoteName,
|
|
887
|
+
b.name,
|
|
888
|
+
cardJson,
|
|
889
|
+
b.commitHash
|
|
890
|
+
);
|
|
891
|
+
if (result.success) {
|
|
892
|
+
await setRemoteRef(nitDir, remoteName, b.name, b.commitHash);
|
|
893
|
+
}
|
|
894
|
+
results.push(result);
|
|
895
|
+
}
|
|
896
|
+
return results;
|
|
897
|
+
}
|
|
898
|
+
async function remote(options) {
|
|
899
|
+
const nitDir = findNitDir(options?.projectDir);
|
|
900
|
+
const card = await readWorkingCard(nitDir);
|
|
901
|
+
const credential = await getRemoteCredential(nitDir, "origin");
|
|
902
|
+
return {
|
|
903
|
+
name: "origin",
|
|
904
|
+
url: card.url || "(not set)",
|
|
905
|
+
hasCredential: credential !== null
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export {
|
|
910
|
+
formatPublicKeyField,
|
|
911
|
+
parsePublicKeyField,
|
|
912
|
+
signChallenge,
|
|
913
|
+
verifySignature,
|
|
914
|
+
diffCards,
|
|
915
|
+
formatDiff,
|
|
916
|
+
fetchBranchCard,
|
|
917
|
+
findNitDir,
|
|
918
|
+
init,
|
|
919
|
+
status,
|
|
920
|
+
commit,
|
|
921
|
+
log,
|
|
922
|
+
diff,
|
|
923
|
+
branch,
|
|
924
|
+
checkout,
|
|
925
|
+
push,
|
|
926
|
+
remote
|
|
927
|
+
};
|