@assethub/cli 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +349 -5
  2. package/dist/autopilot.d.ts +13 -0
  3. package/dist/autopilot.d.ts.map +1 -0
  4. package/dist/autopilot.js +158 -0
  5. package/dist/canvas.d.ts +27 -0
  6. package/dist/canvas.d.ts.map +1 -0
  7. package/dist/canvas.js +82 -0
  8. package/dist/canvasAssetImport.d.ts +45 -0
  9. package/dist/canvasAssetImport.d.ts.map +1 -0
  10. package/dist/canvasAssetImport.js +154 -0
  11. package/dist/canvasComparison.d.ts +21 -0
  12. package/dist/canvasComparison.d.ts.map +1 -0
  13. package/dist/canvasComparison.js +58 -0
  14. package/dist/execution.d.ts +175 -0
  15. package/dist/execution.d.ts.map +1 -0
  16. package/dist/execution.js +234 -0
  17. package/dist/index.d.ts +94 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1579 -189
  20. package/dist/projectSources.d.ts +65 -0
  21. package/dist/projectSources.d.ts.map +1 -0
  22. package/dist/projectSources.js +369 -0
  23. package/dist/projectSourcesWorkbook.d.ts +3 -0
  24. package/dist/projectSourcesWorkbook.d.ts.map +1 -0
  25. package/dist/projectSourcesWorkbook.js +143 -0
  26. package/dist/runsUpload/artifactGraphModel.d.ts +48 -0
  27. package/dist/runsUpload/artifactGraphModel.d.ts.map +1 -0
  28. package/dist/runsUpload/artifactGraphModel.js +127 -0
  29. package/dist/runsUpload/blobRefs.d.ts +13 -0
  30. package/dist/runsUpload/blobRefs.d.ts.map +1 -0
  31. package/dist/runsUpload/blobRefs.js +73 -0
  32. package/dist/runsUpload/buildRunUpload.d.ts +63 -0
  33. package/dist/runsUpload/buildRunUpload.d.ts.map +1 -0
  34. package/dist/runsUpload/buildRunUpload.js +428 -0
  35. package/dist/runsUpload/canonicalJson.d.ts +5 -0
  36. package/dist/runsUpload/canonicalJson.d.ts.map +1 -0
  37. package/dist/runsUpload/canonicalJson.js +108 -0
  38. package/dist/runsUpload/controlEndpoints.d.ts +63 -0
  39. package/dist/runsUpload/controlEndpoints.d.ts.map +1 -0
  40. package/dist/runsUpload/controlEndpoints.js +64 -0
  41. package/dist/runsUpload/graphFolder.d.ts +25 -0
  42. package/dist/runsUpload/graphFolder.d.ts.map +1 -0
  43. package/dist/runsUpload/graphFolder.js +147 -0
  44. package/dist/runsUpload/runUploadError.d.ts +23 -0
  45. package/dist/runsUpload/runUploadError.d.ts.map +1 -0
  46. package/dist/runsUpload/runUploadError.js +24 -0
  47. package/dist/runsUpload/uploadRun.d.ts +43 -0
  48. package/dist/runsUpload/uploadRun.d.ts.map +1 -0
  49. package/dist/runsUpload/uploadRun.js +217 -0
  50. package/package.json +11 -8
@@ -0,0 +1,65 @@
1
+ declare const SCHEMA = "assethub.project-sources.v1";
2
+ type SourceStatus = 'complete' | 'partial' | 'failed' | 'listed';
3
+ type Dependency = {
4
+ command: string;
5
+ available: boolean;
6
+ };
7
+ type Placement = {
8
+ sheet: string;
9
+ cell?: string;
10
+ mapping: string;
11
+ };
12
+ export type ProjectSourceArtifact = {
13
+ relativePath: string;
14
+ sha256: string;
15
+ byteSize: number;
16
+ kind: 'text' | 'metadata' | 'image';
17
+ origin: {
18
+ sourceRelativePath: string;
19
+ sourceSha256: string;
20
+ extraction: 'copy' | 'text' | 'pdf_page' | 'pdf_text' | 'workbook_media' | 'workbook_cells' | 'video_frame' | 'video_metadata';
21
+ page?: number;
22
+ requestedTimestampSeconds?: number;
23
+ workbookMediaPath?: string;
24
+ placements?: Placement[];
25
+ };
26
+ };
27
+ export type ProjectSourceFile = {
28
+ relativePath: string;
29
+ sha256: string;
30
+ byteSize: number;
31
+ format: string;
32
+ origin: {
33
+ sourceRoot: string;
34
+ relativePath: string;
35
+ };
36
+ status: SourceStatus;
37
+ artifactPaths: string[];
38
+ warnings: string[];
39
+ };
40
+ export type ProjectSourcesSummary = {
41
+ schemaVersion: typeof SCHEMA;
42
+ sourceDir: string;
43
+ outDir: string;
44
+ status: 'complete' | 'partial';
45
+ dependencies: Dependency[];
46
+ sources: ProjectSourceFile[];
47
+ images: ProjectSourceArtifact[];
48
+ artifacts: ProjectSourceArtifact[];
49
+ requestedExcludedDirectories: string[];
50
+ excludedDirectories: string[];
51
+ warnings: string[];
52
+ summaryPath: string;
53
+ textPath: string;
54
+ imagesManifestPath: string;
55
+ };
56
+ export type IngestProjectSourcesOptions = {
57
+ sourceDir: string;
58
+ outDir: string;
59
+ excludedDirectories?: string[];
60
+ onProgress?: (message: string) => void;
61
+ };
62
+ /** Extract local bytes and provenance only. Semantic analysis and upload are separate CLI steps. */
63
+ export declare function ingestProjectSources(options: IngestProjectSourcesOptions): Promise<ProjectSourcesSummary>;
64
+ export {};
65
+ //# sourceMappingURL=projectSources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectSources.d.ts","sourceRoot":"","sources":["../src/projectSources.ts"],"names":[],"mappings":"AAkBA,QAAA,MAAM,MAAM,gCAAgC,CAAA;AAwC5C,KAAK,YAAY,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAChE,KAAK,UAAU,GAAG;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAC,CAAA;AACvD,KAAK,SAAS,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAA;AAChE,MAAM,MAAM,qBAAqB,GAAG;IAClC,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;IACnC,MAAM,EAAE;QACN,kBAAkB,EAAE,MAAM,CAAA;QAC1B,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EACN,MAAM,GACN,MAAM,GACN,UAAU,GACV,UAAU,GACV,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,gBAAgB,CAAA;QACpB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,yBAAyB,CAAC,EAAE,MAAM,CAAA;QAClC,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,UAAU,CAAC,EAAE,SAAS,EAAE,CAAA;KACzB,CAAA;CACF,CAAA;AACD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,CAAA;IAClD,MAAM,EAAE,YAAY,CAAA;IACpB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB,CAAA;AACD,MAAM,MAAM,qBAAqB,GAAG;IAClC,aAAa,EAAE,OAAO,MAAM,CAAA;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;IAC9B,YAAY,EAAE,UAAU,EAAE,CAAA;IAC1B,OAAO,EAAE,iBAAiB,EAAE,CAAA;IAC5B,MAAM,EAAE,qBAAqB,EAAE,CAAA;IAC/B,SAAS,EAAE,qBAAqB,EAAE,CAAA;IAClC,4BAA4B,EAAE,MAAM,EAAE,CAAA;IACtC,mBAAmB,EAAE,MAAM,EAAE,CAAA;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,kBAAkB,EAAE,MAAM,CAAA;CAC3B,CAAA;AACD,MAAM,MAAM,2BAA2B,GAAG;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC9B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACvC,CAAA;AA6DD,oGAAoG;AACpG,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,qBAAqB,CAAC,CAgUhC"}
@@ -0,0 +1,369 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { createReadStream } from 'node:fs';
4
+ import { copyFile, mkdir, readFile, readdir, realpath, stat, writeFile, } from 'node:fs/promises';
5
+ import { extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
+ import { promisify } from 'node:util';
7
+ import { EXTRACT_WORKBOOK_PYTHON } from './projectSourcesWorkbook.js';
8
+ const execute = promisify(execFile);
9
+ const SCHEMA = 'assethub.project-sources.v1';
10
+ const MARKER = '.assethub-project-ingest.json';
11
+ const IMAGE_EXTENSIONS = new Set([
12
+ '.png',
13
+ '.jpg',
14
+ '.jpeg',
15
+ '.webp',
16
+ '.gif',
17
+ '.avif',
18
+ '.tif',
19
+ '.tiff',
20
+ '.bmp',
21
+ '.svg',
22
+ '.exr',
23
+ '.hdr',
24
+ '.heic',
25
+ '.ico',
26
+ ]);
27
+ const TEXT_EXTENSIONS = new Set([
28
+ '.txt',
29
+ '.md',
30
+ '.csv',
31
+ '.tsv',
32
+ '.json',
33
+ '.jsonl',
34
+ '.yaml',
35
+ '.yml',
36
+ '.xml',
37
+ '.srt',
38
+ '.vtt',
39
+ ]);
40
+ const VIDEO_EXTENSIONS = new Set([
41
+ '.mov',
42
+ '.mp4',
43
+ '.m4v',
44
+ '.webm',
45
+ '.avi',
46
+ '.mkv',
47
+ ]);
48
+ async function hashFile(path) {
49
+ const hash = createHash('sha256');
50
+ for await (const chunk of createReadStream(path))
51
+ hash.update(chunk);
52
+ return hash.digest('hex');
53
+ }
54
+ function portable(path) {
55
+ return path.split(sep).join('/');
56
+ }
57
+ async function command(command, args) {
58
+ const result = await execute(command, args, {
59
+ encoding: 'utf8',
60
+ maxBuffer: 8 * 1024 * 1024,
61
+ timeout: 300_000,
62
+ });
63
+ return result.stdout;
64
+ }
65
+ async function detectDependencies() {
66
+ return Promise.all([
67
+ ['python3', '--version'],
68
+ ['pdftotext', '-v'],
69
+ ['pdftoppm', '-v'],
70
+ ['ffmpeg', '-version'],
71
+ ['ffprobe', '-version'],
72
+ ].map(async ([name, flag]) => {
73
+ try {
74
+ await command(name, [flag]);
75
+ return { command: name, available: true };
76
+ }
77
+ catch {
78
+ return { command: name, available: false };
79
+ }
80
+ }));
81
+ }
82
+ async function isIngestDirectory(path) {
83
+ try {
84
+ const marker = JSON.parse(await readFile(join(path, MARKER), 'utf8'));
85
+ return (typeof marker === 'object' &&
86
+ marker !== null &&
87
+ 'schemaVersion' in marker &&
88
+ marker.schemaVersion === SCHEMA);
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ /** Extract local bytes and provenance only. Semantic analysis and upload are separate CLI steps. */
95
+ export async function ingestProjectSources(options) {
96
+ const sourceDir = await realpath(resolve(options.sourceDir));
97
+ if (!(await stat(sourceDir)).isDirectory())
98
+ throw new Error('Source must be a directory');
99
+ const excludedPaths = new Set();
100
+ for (const directory of options.excludedDirectories ?? []) {
101
+ const path = resolve(sourceDir, directory);
102
+ const confined = (candidate) => {
103
+ const child = relative(sourceDir, candidate);
104
+ return (child !== '' &&
105
+ child !== '..' &&
106
+ !child.startsWith(`..${sep}`) &&
107
+ !isAbsolute(child));
108
+ };
109
+ if (!directory.trim() || isAbsolute(directory) || !confined(path))
110
+ throw new Error('Excluded directories must be relative paths inside the source root; excluding the source root itself is not allowed');
111
+ try {
112
+ if (!confined(await realpath(path)))
113
+ throw new Error('Excluded directories must stay inside the source root, including symbolic links');
114
+ if (!(await stat(path)).isDirectory())
115
+ throw new Error('Excluded paths must name directories inside the source root');
116
+ }
117
+ catch (error) {
118
+ if (!(error &&
119
+ typeof error === 'object' &&
120
+ 'code' in error &&
121
+ error.code === 'ENOENT'))
122
+ throw error;
123
+ }
124
+ excludedPaths.add(path);
125
+ }
126
+ await mkdir(resolve(options.outDir), { recursive: true });
127
+ const outDir = await realpath(resolve(options.outDir));
128
+ if (sourceDir === outDir)
129
+ throw new Error('Output directory must differ from the source directory');
130
+ const existing = await readdir(outDir);
131
+ if (existing.length && !(await isIngestDirectory(outDir))) {
132
+ throw new Error('Output directory must be empty or previously created by project ingest');
133
+ }
134
+ await writeFile(join(outDir, MARKER), `${JSON.stringify({ schemaVersion: SCHEMA })}\n`);
135
+ const dependencies = await detectDependencies();
136
+ const available = new Set(dependencies.filter(dep => dep.available).map(dep => dep.command));
137
+ const summary = {
138
+ schemaVersion: SCHEMA,
139
+ sourceDir,
140
+ outDir,
141
+ status: 'complete',
142
+ dependencies,
143
+ sources: [],
144
+ images: [],
145
+ artifacts: [],
146
+ requestedExcludedDirectories: [...excludedPaths]
147
+ .map(path => portable(relative(sourceDir, path)))
148
+ .sort(),
149
+ excludedDirectories: [],
150
+ warnings: [],
151
+ summaryPath: join(outDir, 'summary.json'),
152
+ textPath: join(outDir, 'text.md'),
153
+ imagesManifestPath: join(outDir, 'images.json'),
154
+ };
155
+ const files = [];
156
+ async function collect(directory) {
157
+ const entries = (await readdir(directory, { withFileTypes: true })).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
158
+ for (const entry of entries) {
159
+ const path = join(directory, entry.name);
160
+ if (entry.isSymbolicLink()) {
161
+ summary.warnings.push(`Skipped symbolic link: ${portable(relative(sourceDir, path))}`);
162
+ }
163
+ else if (entry.isDirectory()) {
164
+ if (excludedPaths.has(path) ||
165
+ path === outDir ||
166
+ (await isIngestDirectory(path))) {
167
+ summary.excludedDirectories.push(portable(relative(sourceDir, path)));
168
+ }
169
+ else
170
+ await collect(path);
171
+ }
172
+ else if (entry.isFile())
173
+ files.push(path);
174
+ }
175
+ }
176
+ await collect(sourceDir);
177
+ const textSections = [
178
+ '# Extracted project sources',
179
+ '',
180
+ 'Source facts and byte provenance only. Image filenames and worksheet columns are not semantic or 3D classifications.',
181
+ '',
182
+ ];
183
+ for (const path of files) {
184
+ const sourceRelativePath = portable(relative(sourceDir, path));
185
+ options.onProgress?.(`Extracting ${sourceRelativePath}`);
186
+ const sha256 = await hashFile(path);
187
+ const format = extname(path).toLowerCase();
188
+ const source = {
189
+ relativePath: sourceRelativePath,
190
+ sha256,
191
+ byteSize: (await stat(path)).size,
192
+ format: format.slice(1) || 'unknown',
193
+ origin: { sourceRoot: sourceDir, relativePath: sourceRelativePath },
194
+ status: 'complete',
195
+ artifactPaths: [],
196
+ warnings: [],
197
+ };
198
+ summary.sources.push(source);
199
+ const key = createHash('sha256')
200
+ .update(sourceRelativePath)
201
+ .digest('hex')
202
+ .slice(0, 16);
203
+ const folder = join(outDir, 'files', `${key}-${sha256.slice(0, 16)}`);
204
+ await mkdir(folder, { recursive: true });
205
+ const origin = { sourceRelativePath, sourceSha256: sha256 };
206
+ async function artifact(file, kind, detail) {
207
+ const relativePath = portable(relative(outDir, file));
208
+ if (relativePath.startsWith('../') || isAbsolute(relativePath))
209
+ throw new Error('Extraction artifact escaped output directory');
210
+ const result = {
211
+ relativePath,
212
+ sha256: await hashFile(file),
213
+ byteSize: (await stat(file)).size,
214
+ kind,
215
+ origin: { ...origin, ...detail },
216
+ };
217
+ summary.artifacts.push(result);
218
+ source.artifactPaths.push(relativePath);
219
+ if (kind === 'image')
220
+ summary.images.push(result);
221
+ }
222
+ function needs(...names) {
223
+ const missing = names.filter(name => !available.has(name));
224
+ if (!missing.length)
225
+ return true;
226
+ source.status = 'partial';
227
+ source.warnings.push(`Missing dependencies: ${missing.join(', ')}. Install them explicitly and rerun; no installation was attempted.`);
228
+ return false;
229
+ }
230
+ async function readText(file, extraction) {
231
+ await artifact(file, 'text', { extraction });
232
+ textSections.push(`## Source: ${sourceRelativePath}`, '', await readFile(file, 'utf8'), '');
233
+ }
234
+ try {
235
+ if (IMAGE_EXTENSIONS.has(format)) {
236
+ const file = join(folder, `original${format}`);
237
+ await copyFile(path, file);
238
+ await artifact(file, 'image', { extraction: 'copy' });
239
+ }
240
+ else if (TEXT_EXTENSIONS.has(format)) {
241
+ const file = join(folder, 'text.txt');
242
+ await copyFile(path, file);
243
+ await readText(file, 'text');
244
+ }
245
+ else if (format === '.pdf') {
246
+ if (needs('pdftotext')) {
247
+ const file = join(folder, 'text.txt');
248
+ await command('pdftotext', ['-layout', path, file]);
249
+ await readText(file, 'pdf_text');
250
+ }
251
+ if (needs('pdftoppm')) {
252
+ await command('pdftoppm', [
253
+ '-scale-to',
254
+ '1600',
255
+ '-png',
256
+ path,
257
+ join(folder, 'page'),
258
+ ]);
259
+ const pages = (await readdir(folder))
260
+ .filter(name => /^page-\d+\.png$/.test(name))
261
+ .sort((a, b) => Number(a.match(/\d+/)?.[0]) - Number(b.match(/\d+/)?.[0]));
262
+ for (const page of pages)
263
+ await artifact(join(folder, page), 'image', {
264
+ extraction: 'pdf_page',
265
+ page: Number(page.match(/\d+/)?.[0]),
266
+ });
267
+ }
268
+ }
269
+ else if (format === '.xlsx' || format === '.xlsm') {
270
+ if (needs('python3')) {
271
+ await command('python3', [
272
+ '-c',
273
+ EXTRACT_WORKBOOK_PYTHON,
274
+ path,
275
+ folder,
276
+ ]);
277
+ const workbook = JSON.parse(await readFile(join(folder, 'workbook.json'), 'utf8'));
278
+ await artifact(join(folder, 'workbook.json'), 'metadata', {
279
+ extraction: 'workbook_cells',
280
+ });
281
+ await readText(join(folder, 'text.txt'), 'workbook_cells');
282
+ for (const image of workbook.images) {
283
+ await artifact(join(folder, image.file), 'image', {
284
+ extraction: 'workbook_media',
285
+ workbookMediaPath: image.workbookMediaPath,
286
+ placements: image.placements,
287
+ });
288
+ }
289
+ source.warnings.push(...workbook.warnings);
290
+ if (workbook.warnings.length)
291
+ source.status = 'partial';
292
+ }
293
+ }
294
+ else if (VIDEO_EXTENSIONS.has(format)) {
295
+ if (needs('ffprobe')) {
296
+ const metadataText = await command('ffprobe', [
297
+ '-v',
298
+ 'error',
299
+ '-show_format',
300
+ '-show_streams',
301
+ '-of',
302
+ 'json',
303
+ path,
304
+ ]);
305
+ const metadataFile = join(folder, 'video.json');
306
+ await writeFile(metadataFile, metadataText);
307
+ await artifact(metadataFile, 'metadata', {
308
+ extraction: 'video_metadata',
309
+ });
310
+ const metadata = JSON.parse(metadataText);
311
+ const video = metadata.streams?.find(stream => stream.codec_type === 'video');
312
+ if (!video)
313
+ throw new Error('No video stream found');
314
+ if (needs('ffmpeg')) {
315
+ const duration = Number(video.duration ?? metadata.format?.duration);
316
+ const times = Number.isFinite(duration) && duration > 0
317
+ ? [0, duration * 0.5, duration * 0.9]
318
+ : [0];
319
+ if (times.length === 1)
320
+ source.warnings.push('Duration unavailable; extracted the first video frame only.');
321
+ for (let index = 0; index < times.length; index++) {
322
+ const time = Number(times[index].toFixed(6));
323
+ const file = join(folder, `frame-${index + 1}.png`);
324
+ await command('ffmpeg', [
325
+ '-v',
326
+ 'error',
327
+ '-nostdin',
328
+ '-y',
329
+ '-ss',
330
+ String(time),
331
+ '-i',
332
+ path,
333
+ '-map',
334
+ '0:v:0',
335
+ '-frames:v',
336
+ '1',
337
+ file,
338
+ ]);
339
+ await artifact(file, 'image', {
340
+ extraction: 'video_frame',
341
+ requestedTimestampSeconds: time,
342
+ });
343
+ }
344
+ }
345
+ }
346
+ else
347
+ needs('ffmpeg');
348
+ }
349
+ else {
350
+ source.status = 'listed';
351
+ }
352
+ }
353
+ catch (error) {
354
+ source.status = source.artifactPaths.length ? 'partial' : 'failed';
355
+ source.warnings.push((error instanceof Error ? error.message : String(error)).slice(0, 3000));
356
+ }
357
+ if ((await hashFile(path)) !== sha256) {
358
+ source.status = 'failed';
359
+ source.warnings.push('Source changed during extraction; provenance is not reliable. Rerun after source changes stop.');
360
+ }
361
+ if (source.status === 'partial' || source.status === 'failed')
362
+ summary.status = 'partial';
363
+ summary.warnings.push(...source.warnings.map(warning => `${sourceRelativePath}: ${warning}`));
364
+ }
365
+ await writeFile(summary.textPath, `${textSections.join('\n')}\n`);
366
+ await writeFile(summary.imagesManifestPath, `${JSON.stringify({ schemaVersion: SCHEMA, sourceDir, outDir, images: summary.images }, null, 2)}\n`);
367
+ await writeFile(summary.summaryPath, `${JSON.stringify(summary, null, 2)}\n`);
368
+ return summary;
369
+ }
@@ -0,0 +1,3 @@
1
+ /** Standard-library-only helper embedded in the published CLI by TypeScript compilation. */
2
+ export declare const EXTRACT_WORKBOOK_PYTHON: string;
3
+ //# sourceMappingURL=projectSourcesWorkbook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projectSourcesWorkbook.d.ts","sourceRoot":"","sources":["../src/projectSourcesWorkbook.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,eAAO,MAAM,uBAAuB,QA6InC,CAAA"}
@@ -0,0 +1,143 @@
1
+ /** Standard-library-only helper embedded in the published CLI by TypeScript compilation. */
2
+ export const EXTRACT_WORKBOOK_PYTHON = String.raw `
3
+ import json, posixpath, sys, zipfile
4
+ from pathlib import Path
5
+ from xml.etree import ElementTree as E
6
+
7
+ source, output = Path(sys.argv[1]), Path(sys.argv[2])
8
+ ns = {
9
+ 's': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
10
+ 'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
11
+ 'rd': 'http://schemas.microsoft.com/office/spreadsheetml/2017/richdata',
12
+ 'xdr': 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing',
13
+ 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
14
+ }
15
+ output.mkdir(parents=True, exist_ok=True)
16
+ result = {'sheets': [], 'images': [], 'warnings': []}
17
+ with zipfile.ZipFile(source) as z:
18
+ names = set(z.namelist())
19
+ def xml(path):
20
+ return E.fromstring(z.read(path))
21
+ def resolve(part, target):
22
+ return target.lstrip('/') if target.startswith('/') else posixpath.normpath(posixpath.join(posixpath.dirname(part), target))
23
+ def rels(part):
24
+ name = posixpath.join(posixpath.dirname(part), '_rels', posixpath.basename(part) + '.rels')
25
+ if name not in names:
26
+ return {}
27
+ return {e.get('Id'): resolve(part, e.get('Target', '')) for e in xml(name) if e.get('TargetMode') != 'External'}
28
+ def col_name(number):
29
+ result = ''
30
+ while number:
31
+ number, remainder = divmod(number - 1, 26)
32
+ result = chr(65 + remainder) + result
33
+ return result
34
+ strings = []
35
+ if 'xl/sharedStrings.xml' in names:
36
+ strings = [''.join(n.text or '' for n in e.iter('{'+ns['s']+'}t')) for e in xml('xl/sharedStrings.xml')]
37
+ placements = {}
38
+ def place(target, value):
39
+ if target in names:
40
+ placements.setdefault(target, []).append(value)
41
+ else:
42
+ result['warnings'].append('Image relationship target is missing: ' + str(target))
43
+ # Excel rich-value images use cell vm -> value metadata -> future metadata -> rich value -> relationship.
44
+ rich_paths = ['xl/richData/richValueRel.xml', 'xl/richData/rdrichvalue.xml', 'xl/metadata.xml']
45
+ rich_targets = {}
46
+ if all(p in names for p in rich_paths):
47
+ try:
48
+ rich_part = rich_paths[0]
49
+ relationships = rels(rich_part)
50
+ relation_ids = [e.get('{'+ns['r']+'}id') for e in xml(rich_part)]
51
+ values = [[e.text for e in row] for row in xml(rich_paths[1])]
52
+ metadata = xml(rich_paths[2])
53
+ futures = next(e for e in metadata.findall('s:futureMetadata', ns) if e.get('name') == 'XLRICHVALUE')
54
+ future_indexes = [int(next(e.iter('{'+ns['rd']+'}rvb')).get('i')) for e in futures]
55
+ metadata_types = metadata.find('s:metadataTypes', ns)
56
+ rich_type = next(i+1 for i,e in enumerate(metadata_types) if e.get('name') == 'XLRICHVALUE')
57
+ local_image_fields = []
58
+ structure_path = 'xl/richData/rdrichvaluestructure.xml'
59
+ if structure_path in names:
60
+ for structure in xml(structure_path):
61
+ local_image_fields.append(next((i for i,k in enumerate(structure) if k.get('n') == '_rvRel:LocalImageIdentifier'), 0))
62
+ rich_rows = list(xml(rich_paths[1]))
63
+ for index, block in enumerate(metadata.find('s:valueMetadata', ns)):
64
+ rc = next((e for e in block if int(e.get('t', '0')) == rich_type), None)
65
+ if rc is None:
66
+ continue
67
+ value_index = future_indexes[int(rc.get('v'))]
68
+ structure_index = int(rich_rows[value_index].get('s', '0'))
69
+ field = local_image_fields[structure_index] if structure_index < len(local_image_fields) else 0
70
+ relation_index = int(values[value_index][field])
71
+ rich_targets[index+1] = relationships[relation_ids[relation_index]]
72
+ except (KeyError, IndexError, ValueError, TypeError, StopIteration) as error:
73
+ result['warnings'].append('Rich-value cell mapping incomplete: ' + type(error).__name__ + ': ' + str(error))
74
+ book_rels = rels('xl/workbook.xml')
75
+ for sheet in xml('xl/workbook.xml').find('s:sheets', ns):
76
+ name = sheet.get('name', '')
77
+ part = book_rels[sheet.get('{'+ns['r']+'}id')]
78
+ document = xml(part)
79
+ cells = []
80
+ for cell in document.findall('.//s:sheetData/s:row/s:c', ns):
81
+ address = cell.get('r')
82
+ kind = cell.get('t', 'n')
83
+ value_node = cell.find('s:v', ns)
84
+ cached = value_node.text if value_node is not None else None
85
+ if kind == 's' and cached is not None:
86
+ value = strings[int(cached)]
87
+ elif kind == 'inlineStr':
88
+ value = ''.join(n.text or '' for n in cell.findall('.//s:t', ns))
89
+ else:
90
+ value = cached
91
+ formula = cell.find('s:f', ns)
92
+ record = {'cell': address, 'type': kind, 'value': value}
93
+ if formula is not None:
94
+ record['formula'] = formula.text or ''
95
+ if value is not None or formula is not None:
96
+ cells.append(record)
97
+ vm = cell.get('vm')
98
+ if vm:
99
+ target = rich_targets.get(int(vm))
100
+ if target:
101
+ place(target, {'sheet': name, 'cell': address, 'mapping': 'rich_value_cell'})
102
+ else:
103
+ result['warnings'].append('Unresolved image metadata at ' + name + '!' + str(address))
104
+ result['sheets'].append({'name': name, 'part': part, 'cells': cells})
105
+ sheet_rels = rels(part)
106
+ for drawing in document.findall('s:drawing', ns):
107
+ drawing_part = sheet_rels.get(drawing.get('{'+ns['r']+'}id'))
108
+ if not drawing_part or drawing_part not in names:
109
+ result['warnings'].append('Drawing relationship missing in ' + name)
110
+ continue
111
+ drawing_rels = rels(drawing_part)
112
+ for anchor in xml(drawing_part):
113
+ start = anchor.find('xdr:from', ns)
114
+ placement = {'sheet': name, 'mapping': 'drawing_anchor'}
115
+ if start is not None:
116
+ row = int(start.find('xdr:row', ns).text)+1
117
+ column = int(start.find('xdr:col', ns).text)+1
118
+ placement['cell'] = col_name(column) + str(row)
119
+ for blip in anchor.iter():
120
+ target = drawing_rels.get(blip.get('{'+ns['r']+'}embed'))
121
+ if target and target.startswith('xl/media/'):
122
+ image_placement = placement.copy()
123
+ if blip.tag.endswith('}imgLayer'):
124
+ image_placement['mapping'] = 'drawing_image_layer'
125
+ place(target, image_placement)
126
+ # Preserve every original media byte, including images whose placement cannot be resolved.
127
+ for index, media in enumerate(sorted(n for n in names if n.startswith('xl/media/') and not n.endswith('/'))):
128
+ filename = 'image-' + str(index+1).zfill(4) + Path(media).suffix.lower()
129
+ (output / filename).write_bytes(z.read(media))
130
+ result['images'].append({'file': filename, 'workbookMediaPath': media, 'placements': placements.get(media, [])})
131
+ if media not in placements:
132
+ result['warnings'].append('Image extracted without cell mapping: ' + media)
133
+ (output / 'workbook.json').write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
134
+ lines = []
135
+ for sheet in result['sheets']:
136
+ lines.append('## Sheet: ' + sheet['name'])
137
+ for cell in sheet['cells']:
138
+ value = '' if cell['value'] is None else str(cell['value'])
139
+ formula = (' [formula: ' + cell['formula'] + ']') if 'formula' in cell else ''
140
+ lines.append(cell['cell'] + ': ' + value + formula)
141
+ lines.append('')
142
+ (output / 'text.txt').write_text('\n'.join(lines) + '\n', encoding='utf-8')
143
+ `;
@@ -0,0 +1,48 @@
1
+ export { canonicalJson, isPlainObject, sha256Hex } from './canonicalJson.js';
2
+ /** `ag.commit.v1` artifact node — output shape of Python `_normalize_artifact`. */
3
+ export type ArtifactNode = {
4
+ id: string;
5
+ artifactKind: string;
6
+ tags: string[];
7
+ metadata: Record<string, unknown>;
8
+ semanticType?: string;
9
+ lifecycle?: string;
10
+ payload?: Record<string, unknown>;
11
+ schemaRef?: string;
12
+ createdBy?: string;
13
+ createdAt?: string;
14
+ updatedAt?: string;
15
+ };
16
+ /** Artifact edge — output shape of Python `_normalize_edge`. */
17
+ export type ArtifactEdge = {
18
+ id: string;
19
+ from: string;
20
+ to: string;
21
+ kind: string;
22
+ tags: string[];
23
+ metadata: Record<string, unknown>;
24
+ createdBy?: string;
25
+ };
26
+ export type ArtifactGraph = {
27
+ nodes: ArtifactNode[];
28
+ edges: ArtifactEdge[];
29
+ };
30
+ export declare const codePointCompare: (a: string, b: string) => number;
31
+ /** Port of `_normalize_artifact`: returns null for anything that is not a
32
+ * valid `ag.commit.v1` artifact; keeps only the schema's fields. */
33
+ export declare const normalizeArtifact: (value: unknown) => ArtifactNode | null;
34
+ /** Port of `_normalize_edge`. */
35
+ export declare const normalizeEdge: (value: unknown) => ArtifactEdge | null;
36
+ /** Port of `normalize_graph_state`: dedupe by id (last wins), drop edges with
37
+ * missing endpoints, sort both arrays by id (code-point order). Idempotent —
38
+ * a reader normalizes our JSONL lines and must get them back byte-identical,
39
+ * or the manifest hash won't verify. */
40
+ export declare const normalizeArtifactGraph: (value: {
41
+ nodes?: unknown[];
42
+ edges?: unknown[];
43
+ } | null) => ArtifactGraph;
44
+ /** Port of `canonical_graph_hash` — callers pass normalizeArtifactGraph output. */
45
+ export declare const canonicalGraphHash: (graph: ArtifactGraph) => string;
46
+ /** Port of `canonical_json_line` (graph_folder/format.py) — one JSONL line. */
47
+ export declare const canonicalJsonLine: (value: unknown) => string;
48
+ //# sourceMappingURL=artifactGraphModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifactGraphModel.d.ts","sourceRoot":"","sources":["../../src/runsUpload/artifactGraphModel.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,aAAa,EAAE,aAAa,EAAE,SAAS,EAAC,MAAM,oBAAoB,CAAA;AAE1E,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,gEAAgE;AAChE,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAAC,CAAA;AAiB1E,eAAO,MAAM,gBAAgB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAAsC,CAAA;AAmB9F;qEACqE;AACrE,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,YAAY,GAAG,IAqBjE,CAAA;AAED,iCAAiC;AACjC,eAAO,MAAM,aAAa,GAAI,OAAO,OAAO,KAAG,YAAY,GAAG,IAkB7D,CAAA;AAWD;;;yCAGyC;AACzC,eAAO,MAAM,sBAAsB,GACjC,OAAO;IAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;CAAC,GAAG,IAAI,KACnD,aAiBF,CAAA;AAED,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,GAAI,OAAO,aAAa,KAAG,MACb,CAAA;AAE7C,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,MAA8B,CAAA"}