@trydreamstate/cli 1.3.1 → 1.3.3
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/README.md +29 -16
- package/agent-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-plugin/.codex-plugin/plugin.json +4 -1
- package/agent-plugin/assets/logo.png +0 -0
- package/agent-plugin/hooks/approve-readonly.sh +15 -0
- package/agent-plugin/hooks/hooks.json +17 -0
- package/agent-plugin/skills/crm/SKILL.md +2 -2
- package/agent-plugin/skills/diagnostics/SKILL.md +1 -1
- package/agent-plugin/skills/dreamstate/SKILL.md +13 -5
- package/agent-plugin/skills/first-win/SKILL.md +2 -2
- package/agent-plugin/skills/inbox-replies/SKILL.md +1 -1
- package/agent-plugin/skills/knowledge/SKILL.md +1 -1
- package/agent-plugin/skills/linkedin-posts/SKILL.md +1 -1
- package/agent-plugin/skills/linkedin-warm-signals/SKILL.md +2 -2
- package/agent-plugin/skills/list-building/SKILL.md +4 -4
- package/agent-plugin/skills/outreach/SKILL.md +1 -1
- package/agent-plugin/skills/outreach/steps-and-timing.md +1 -1
- package/agent-plugin/skills/plays/SKILL.md +2 -2
- package/agent-plugin/skills/reddit-posts/SKILL.md +1 -1
- package/agent-plugin/skills/skill-authoring/SKILL.md +1 -1
- package/agent-plugin/skills/social/SKILL.md +2 -2
- package/agent-plugin/skills/visibility/SKILL.md +2 -2
- package/agent-plugin/skills/workflows/SKILL.md +3 -3
- package/agent-plugin/skills/worksheet-tidy/SKILL.md +1 -1
- package/agent-plugin/skills/writing/SKILL.md +1 -1
- package/agent-plugin/skills/x-posts/SKILL.md +1 -1
- package/dist/agentGuidance.generated.json +35 -0
- package/dist/client.d.ts +31 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +115 -33
- package/dist/commandTable.d.ts +26 -0
- package/dist/commandTable.d.ts.map +1 -0
- package/dist/commandTable.js +54 -0
- package/dist/commands/sequences.d.ts +1 -1
- package/dist/commands/sequences.d.ts.map +1 -1
- package/dist/commands/sequences.js +37 -36
- package/dist/commands/shared.d.ts +4 -26
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/shared.js +129 -127
- package/dist/commands/workflows.d.ts +1 -1
- package/dist/commands/workflows.d.ts.map +1 -1
- package/dist/commands/workflows.js +35 -41
- package/dist/errors.d.ts +78 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +117 -0
- package/dist/gatedRun.d.ts +81 -0
- package/dist/gatedRun.d.ts.map +1 -0
- package/dist/gatedRun.js +370 -0
- package/dist/idempotency.d.ts +38 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +106 -0
- package/dist/index.d.ts +21 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +372 -366
- package/dist/runWait.d.ts +2 -1
- package/dist/runWait.d.ts.map +1 -1
- package/dist/runWait.js +3 -1
- package/dist/schemaCoercion.d.ts +20 -0
- package/dist/schemaCoercion.d.ts.map +1 -0
- package/dist/schemaCoercion.js +123 -0
- package/dist/skillInstaller.d.ts +40 -0
- package/dist/skillInstaller.d.ts.map +1 -1
- package/dist/skillInstaller.js +108 -9
- package/dist/tableFilePreview.d.ts +14 -0
- package/dist/tableFilePreview.d.ts.map +1 -1
- package/dist/tableFilePreview.js +19 -8
- package/dist/tableFileReader.d.ts +26 -0
- package/dist/tableFileReader.d.ts.map +1 -0
- package/dist/tableFileReader.js +108 -0
- package/dist/tablesImport.d.ts +57 -0
- package/dist/tablesImport.d.ts.map +1 -0
- package/dist/tablesImport.js +263 -0
- package/dist/turnCache.d.ts +14 -1
- package/dist/turnCache.d.ts.map +1 -1
- package/dist/turnCache.js +25 -8
- package/package.json +3 -2
package/dist/runWait.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DreamstateClient } from './client';
|
|
2
|
-
|
|
2
|
+
/** The wait ran out with the run still active; the latest run envelope is printed. */
|
|
3
|
+
export declare const RUN_WAIT_TIMEOUT_EXIT_CODE: number;
|
|
3
4
|
export declare function columnRunReturnsRunId(capabilityId: string): boolean;
|
|
4
5
|
export interface RunWaitResult {
|
|
5
6
|
value: unknown;
|
package/dist/runWait.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runWait.d.ts","sourceRoot":"","sources":["../src/runWait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"runWait.d.ts","sourceRoot":"","sources":["../src/runWait.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAQjD,sFAAsF;AACtF,eAAO,MAAM,0BAA0B,EAAE,MAAyB,CAAC;AAYnE,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AA+BD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAC7C,OAAO,CAAC,aAAa,CAAC,CAmBxB"}
|
package/dist/runWait.js
CHANGED
|
@@ -3,11 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RUN_WAIT_TIMEOUT_EXIT_CODE = void 0;
|
|
4
4
|
exports.columnRunReturnsRunId = columnRunReturnsRunId;
|
|
5
5
|
exports.waitForRun = waitForRun;
|
|
6
|
+
const errors_1 = require("./errors");
|
|
6
7
|
const TERMINAL_STATUSES = new Set([
|
|
7
8
|
'succeeded', 'completed', 'completed_with_errors', 'failed', 'cancelled', 'canceled', 'partially_completed',
|
|
8
9
|
'timed_out', 'rejected', 'awaiting_approval', 'awaiting_input', 'waiting', 'waiting_approval', 'approval_required',
|
|
9
10
|
]);
|
|
10
|
-
|
|
11
|
+
/** The wait ran out with the run still active; the latest run envelope is printed. */
|
|
12
|
+
exports.RUN_WAIT_TIMEOUT_EXIT_CODE = errors_1.CLI_EXIT.TIMEOUT;
|
|
11
13
|
const COLUMN_RUN_HAND_OFF = new Set([
|
|
12
14
|
'tables.columns.run',
|
|
13
15
|
'tables.columns.run_all',
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface CoercionResult {
|
|
2
|
+
params: Record<string, unknown>;
|
|
3
|
+
/** Property names whose string value was converted. */
|
|
4
|
+
coerced: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Pack verbs intentionally do not carry a second copy of a capability's required
|
|
8
|
+
* fields. `tools get` is source of truth: reject an unknown canonical flag and
|
|
9
|
+
* report fields that schema currently requires after the live contract is read.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertParamsMatchSchema(params: Record<string, unknown>, schema: unknown): void;
|
|
12
|
+
/**
|
|
13
|
+
* Runtime schema coercion for flag-built params. The CLI's pack verbs take their
|
|
14
|
+
* values from flags, and every flag arrives as a string; a numeric property such
|
|
15
|
+
* as max_new_enrollments_per_day was sent as "40" and refused. The type is read
|
|
16
|
+
* from the capability's live input schema (tools get), never from a hand-kept
|
|
17
|
+
* list, so a new numeric field works on the day the catalog adds it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function coerceParamsToSchema(params: Record<string, unknown>, schema: unknown): CoercionResult;
|
|
20
|
+
//# sourceMappingURL=schemaCoercion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaCoercion.d.ts","sourceRoot":"","sources":["../src/schemaCoercion.ts"],"names":[],"mappings":"AAoEA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,uDAAuD;IACvD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAS9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,GAAG,cAAc,CAerG"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertParamsMatchSchema = assertParamsMatchSchema;
|
|
4
|
+
exports.coerceParamsToSchema = coerceParamsToSchema;
|
|
5
|
+
function isObject(value) {
|
|
6
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
/** Follows a local `#/$defs/x` or `#/definitions/x` pointer (tools get hoists repeated subschemas there). */
|
|
9
|
+
function resolveRef(node, root, depth = 0) {
|
|
10
|
+
const ref = node.$ref;
|
|
11
|
+
if (typeof ref !== 'string' || !ref.startsWith('#/') || depth > 8)
|
|
12
|
+
return node;
|
|
13
|
+
let current = root;
|
|
14
|
+
for (const part of ref.slice(2).split('/')) {
|
|
15
|
+
current = isObject(current) ? current[decodeURIComponent(part)] : undefined;
|
|
16
|
+
}
|
|
17
|
+
return isObject(current) ? resolveRef(current, root, depth + 1) : node;
|
|
18
|
+
}
|
|
19
|
+
/** Every JSON type a (possibly union) schema accepts. */
|
|
20
|
+
function acceptedTypes(node, root, depth = 0) {
|
|
21
|
+
const types = new Set();
|
|
22
|
+
if (!isObject(node) || depth > 8)
|
|
23
|
+
return types;
|
|
24
|
+
const resolved = resolveRef(node, root);
|
|
25
|
+
const declared = resolved.type;
|
|
26
|
+
for (const type of Array.isArray(declared) ? declared : declared === undefined ? [] : [declared]) {
|
|
27
|
+
if (typeof type === 'string')
|
|
28
|
+
types.add(type);
|
|
29
|
+
}
|
|
30
|
+
for (const key of ['anyOf', 'oneOf']) {
|
|
31
|
+
const branches = resolved[key];
|
|
32
|
+
if (Array.isArray(branches))
|
|
33
|
+
for (const branch of branches)
|
|
34
|
+
acceptedTypes(branch, root, depth + 1).forEach((type) => types.add(type));
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(resolved.allOf)) {
|
|
37
|
+
for (const branch of resolved.allOf)
|
|
38
|
+
acceptedTypes(branch, root, depth + 1).forEach((type) => types.add(type));
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(resolved.enum) && resolved.enum.some((member) => typeof member === 'string'))
|
|
41
|
+
types.add('string');
|
|
42
|
+
return types;
|
|
43
|
+
}
|
|
44
|
+
const NUMBER_TEXT = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
45
|
+
/**
|
|
46
|
+
* One flag value, as the shell delivered it (a string), turned into the type the
|
|
47
|
+
* capability's input schema declares. A string-typed property is never touched,
|
|
48
|
+
* so a value the author meant as text (a name that looks like a number) survives.
|
|
49
|
+
*/
|
|
50
|
+
function coerceValue(value, types) {
|
|
51
|
+
if (types.has('string') || types.size === 0)
|
|
52
|
+
return value;
|
|
53
|
+
const text = value.trim();
|
|
54
|
+
if ((types.has('integer') || types.has('number')) && NUMBER_TEXT.test(text)) {
|
|
55
|
+
const parsed = Number(text);
|
|
56
|
+
if (Number.isFinite(parsed) && (types.has('number') || Number.isInteger(parsed)))
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
if (types.has('boolean')) {
|
|
60
|
+
if (text === 'true')
|
|
61
|
+
return true;
|
|
62
|
+
if (text === 'false')
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (types.has('array') || types.has('object')) {
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(text);
|
|
68
|
+
if (Array.isArray(parsed) ? types.has('array') : isObject(parsed) && types.has('object'))
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Not JSON: leave it, and the server's schema refusal names the field.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (types.has('null') && text === 'null')
|
|
76
|
+
return null;
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pack verbs intentionally do not carry a second copy of a capability's required
|
|
81
|
+
* fields. `tools get` is source of truth: reject an unknown canonical flag and
|
|
82
|
+
* report fields that schema currently requires after the live contract is read.
|
|
83
|
+
*/
|
|
84
|
+
function assertParamsMatchSchema(params, schema) {
|
|
85
|
+
if (!isObject(schema) || !isObject(schema.properties))
|
|
86
|
+
return;
|
|
87
|
+
const properties = schema.properties;
|
|
88
|
+
if (Object.keys(properties).length === 0)
|
|
89
|
+
return;
|
|
90
|
+
const unknown = Object.keys(params).filter((key) => !(key in properties));
|
|
91
|
+
if (unknown.length)
|
|
92
|
+
throw new Error(`live schema does not declare parameter(s): ${unknown.join(', ')}`);
|
|
93
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((key) => typeof key === 'string') : [];
|
|
94
|
+
const missing = required.filter((key) => params[key] === undefined);
|
|
95
|
+
if (missing.length)
|
|
96
|
+
throw new Error(`live schema requires parameter(s): ${missing.join(', ')}`);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Runtime schema coercion for flag-built params. The CLI's pack verbs take their
|
|
100
|
+
* values from flags, and every flag arrives as a string; a numeric property such
|
|
101
|
+
* as max_new_enrollments_per_day was sent as "40" and refused. The type is read
|
|
102
|
+
* from the capability's live input schema (tools get), never from a hand-kept
|
|
103
|
+
* list, so a new numeric field works on the day the catalog adds it.
|
|
104
|
+
*/
|
|
105
|
+
function coerceParamsToSchema(params, schema) {
|
|
106
|
+
if (!isObject(schema) || !isObject(schema.properties))
|
|
107
|
+
return { params, coerced: [] };
|
|
108
|
+
const out = { ...params };
|
|
109
|
+
const coerced = [];
|
|
110
|
+
for (const [key, value] of Object.entries(params)) {
|
|
111
|
+
if (typeof value !== 'string')
|
|
112
|
+
continue;
|
|
113
|
+
const property = schema.properties[key];
|
|
114
|
+
if (!isObject(property))
|
|
115
|
+
continue;
|
|
116
|
+
const next = coerceValue(value, acceptedTypes(property, schema));
|
|
117
|
+
if (next !== value) {
|
|
118
|
+
out[key] = next;
|
|
119
|
+
coerced.push(key);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { params: out, coerced };
|
|
123
|
+
}
|
package/dist/skillInstaller.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ export interface HostedSkill {
|
|
|
10
10
|
name: string;
|
|
11
11
|
content: string;
|
|
12
12
|
}>;
|
|
13
|
+
/** The server's revision of SKILL.md; part of the content stamp doctor compares. */
|
|
14
|
+
revision_number?: number;
|
|
15
|
+
/** Supporting pages the server lists for this skill, with their revisions. */
|
|
16
|
+
files?: Array<{
|
|
17
|
+
path: string;
|
|
18
|
+
revision_number?: number;
|
|
19
|
+
}>;
|
|
13
20
|
}
|
|
14
21
|
export interface SkillInstallOptions {
|
|
15
22
|
agents: readonly SkillInstallAgent[];
|
|
@@ -23,6 +30,15 @@ export interface SkillInstallOptions {
|
|
|
23
30
|
export declare function canonicalSkillsDirectory(homeDirectory?: string, project?: boolean, cwd?: string): string;
|
|
24
31
|
export declare function skillInstallDirectory(agent: SkillInstallAgent, homeDirectory?: string, project?: boolean, cwd?: string): string;
|
|
25
32
|
export declare function detectInstalledSkillAgents(homeDirectory?: string): Promise<Array<'codex' | 'claude-code' | 'cursor'>>;
|
|
33
|
+
/**
|
|
34
|
+
* What identifies one skill's live content: its text and frontmatter, the server's
|
|
35
|
+
* revision of it, and the supporting pages with theirs. Install stamps this into
|
|
36
|
+
* every file it writes; `skills doctor` recomputes it from the live skills, so a
|
|
37
|
+
* skill that changed on the server is stale however recently it was installed.
|
|
38
|
+
*/
|
|
39
|
+
export declare function skillContentHash(skill: HostedSkill): string;
|
|
40
|
+
/** Version of the whole skill set, stamped as `skills=` in every installed file. */
|
|
41
|
+
export declare function skillIndexVersion(skills: readonly HostedSkill[]): string;
|
|
26
42
|
export declare function renderSkillMarkdown(skill: HostedSkill, provenance: {
|
|
27
43
|
workspaceId: string;
|
|
28
44
|
indexVersion: string;
|
|
@@ -48,4 +64,28 @@ export declare function inspectSkillInstallers(agents: readonly SkillInstallAgen
|
|
|
48
64
|
}>;
|
|
49
65
|
provenance_age_ms: number | null;
|
|
50
66
|
}>>;
|
|
67
|
+
export interface SkillDrift {
|
|
68
|
+
/** Installed skill directories found under the canonical skills directory. */
|
|
69
|
+
installed_skill_ids: string[];
|
|
70
|
+
live_skill_ids: string[];
|
|
71
|
+
/** Live skills with no installed copy. */
|
|
72
|
+
missing: string[];
|
|
73
|
+
/** Installed Dreamstate skills the server no longer lists. */
|
|
74
|
+
extra: string[];
|
|
75
|
+
/** Installed skills whose content stamp differs from the live content. */
|
|
76
|
+
stale: Array<{
|
|
77
|
+
id: string;
|
|
78
|
+
installed: string | null;
|
|
79
|
+
live: string;
|
|
80
|
+
}>;
|
|
81
|
+
in_sync: boolean;
|
|
82
|
+
/** The command that brings the install up to date. */
|
|
83
|
+
refresh_command: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Compares what is installed with the live skills, by content stamp and not by age.
|
|
87
|
+
* An install an hour old that predates a server-side edit is stale; one from last
|
|
88
|
+
* month that nothing has changed since is current.
|
|
89
|
+
*/
|
|
90
|
+
export declare function compareInstalledSkills(live: readonly HostedSkill[], homeDirectory?: string, project?: boolean, cwd?: string): Promise<SkillDrift>;
|
|
51
91
|
//# sourceMappingURL=skillInstaller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skillInstaller.d.ts","sourceRoot":"","sources":["../src/skillInstaller.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skillInstaller.d.ts","sourceRoot":"","sources":["../src/skillInstaller.ts"],"names":[],"mappings":"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"}
|
package/dist/skillInstaller.js
CHANGED
|
@@ -32,23 +32,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.SKILL_INSTALL_AGENTS = void 0;
|
|
37
40
|
exports.canonicalSkillsDirectory = canonicalSkillsDirectory;
|
|
38
41
|
exports.skillInstallDirectory = skillInstallDirectory;
|
|
39
42
|
exports.detectInstalledSkillAgents = detectInstalledSkillAgents;
|
|
43
|
+
exports.skillContentHash = skillContentHash;
|
|
44
|
+
exports.skillIndexVersion = skillIndexVersion;
|
|
40
45
|
exports.renderSkillMarkdown = renderSkillMarkdown;
|
|
41
46
|
exports.installSkills = installSkills;
|
|
42
47
|
exports.routerSkill = routerSkill;
|
|
43
48
|
exports.inspectSkillInstallers = inspectSkillInstallers;
|
|
49
|
+
exports.compareInstalledSkills = compareInstalledSkills;
|
|
44
50
|
const promises_1 = require("node:fs/promises");
|
|
45
51
|
const node_fs_1 = require("node:fs");
|
|
46
52
|
const node_crypto_1 = require("node:crypto");
|
|
47
53
|
const node_os_1 = require("node:os");
|
|
48
54
|
const node_path_1 = require("node:path");
|
|
49
|
-
const
|
|
55
|
+
const agentGuidance_generated_json_1 = __importDefault(require("./agentGuidance.generated.json"));
|
|
56
|
+
const errors_1 = require("./errors");
|
|
57
|
+
const idempotency_1 = require("./idempotency");
|
|
50
58
|
exports.SKILL_INSTALL_AGENTS = ['codex', 'claude-code', 'cursor', 'opencode', 'hermes', 'openclaw'];
|
|
51
59
|
const allowedTools = 'Bash(dreamstate *), Bash(jq *)';
|
|
60
|
+
const PROVENANCE_MARKER = '<!-- dreamstate provenance:';
|
|
52
61
|
const safeId = /^[a-z0-9][a-z0-9_-]*$/i;
|
|
53
62
|
function canonicalSkillsDirectory(homeDirectory = (0, node_os_1.homedir)(), project = false, cwd = process.cwd()) {
|
|
54
63
|
return project ? (0, node_path_1.join)(cwd, '.agents', 'skills') : (0, node_path_1.join)(homeDirectory, '.agents', 'skills');
|
|
@@ -103,14 +112,42 @@ async function agentInstalled(agent, homeDirectory, project, cwd) {
|
|
|
103
112
|
return false;
|
|
104
113
|
}
|
|
105
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* What identifies one skill's live content: its text and frontmatter, the server's
|
|
117
|
+
* revision of it, and the supporting pages with theirs. Install stamps this into
|
|
118
|
+
* every file it writes; `skills doctor` recomputes it from the live skills, so a
|
|
119
|
+
* skill that changed on the server is stale however recently it was installed.
|
|
120
|
+
*/
|
|
121
|
+
function skillContentHash(skill) {
|
|
122
|
+
return (0, node_crypto_1.createHash)('sha256').update((0, idempotency_1.stableJson)({
|
|
123
|
+
id: skill.id,
|
|
124
|
+
description: skill.description,
|
|
125
|
+
body: skill.body,
|
|
126
|
+
frontmatter: skill.frontmatter ?? {},
|
|
127
|
+
revision_number: skill.revision_number ?? null,
|
|
128
|
+
files: [...(skill.files ?? [])].map((file) => ({ path: file.path, revision_number: file.revision_number ?? null })).sort((a, b) => a.path.localeCompare(b.path)),
|
|
129
|
+
})).digest('hex').slice(0, 16);
|
|
130
|
+
}
|
|
131
|
+
/** Version of the whole skill set, stamped as `skills=` in every installed file. */
|
|
132
|
+
function skillIndexVersion(skills) {
|
|
133
|
+
return (0, node_crypto_1.createHash)('sha256')
|
|
134
|
+
.update(JSON.stringify(skills.map((skill) => [skill.id, skillContentHash(skill)])))
|
|
135
|
+
.digest('hex').slice(0, 16);
|
|
136
|
+
}
|
|
106
137
|
function yamlValue(value) { return JSON.stringify(value); }
|
|
107
138
|
function skillName(id) {
|
|
108
139
|
if (!safeId.test(id))
|
|
109
|
-
throw new
|
|
140
|
+
throw new errors_1.CliUsageError('Dreamstate API returned an invalid skill id.');
|
|
110
141
|
return id === 'router' ? 'dreamstate' : `dreamstate-${id}`;
|
|
111
142
|
}
|
|
143
|
+
// The router steps and the paid-run consent rule are not written here. They come from
|
|
144
|
+
// agentGuidance.generated.json, which packages/cli/scripts/build-agent-plugin.mjs generates
|
|
145
|
+
// from the same source as the plugin router and the server's MCP instructions.
|
|
146
|
+
function routerStepLines() {
|
|
147
|
+
return agentGuidance_generated_json_1.default.router_steps.map((step, index) => `${index + 1}. \`${step.cli}\` (MCP \`${step.mcp}\`) ${step.text}`).join('\n');
|
|
148
|
+
}
|
|
112
149
|
function commandGuide(packs) {
|
|
113
|
-
return `## How to run this in Dreamstate\n\
|
|
150
|
+
return `## How to run this in Dreamstate\n\n${routerStepLines()}\n\n${agentGuidance_generated_json_1.default.consent.summary}\n\nGoals: ${packs.length ? packs.join(', ') : 'not declared'}.`;
|
|
114
151
|
}
|
|
115
152
|
function packs(frontmatter) {
|
|
116
153
|
const value = frontmatter.packs;
|
|
@@ -129,20 +166,41 @@ function renderSkillMarkdown(skill, provenance) {
|
|
|
129
166
|
const lines = Object.entries(frontmatter).flatMap(([key, value]) => Array.isArray(value)
|
|
130
167
|
? [`${key}: [${value.map(yamlValue).join(', ')}]`]
|
|
131
168
|
: [`${key}: ${yamlValue(value)}`]);
|
|
132
|
-
return `---\n${lines.join('\n')}\n---\n\n${commandGuide(packs(frontmatter))}\n\n${skill.body.trim()}\n\n<!-- dreamstate provenance: workspace=${provenance.workspaceId} issued=${new Date().toISOString()} skills=${provenance.indexVersion} -->\n`;
|
|
169
|
+
return `---\n${lines.join('\n')}\n---\n\n${commandGuide(packs(frontmatter))}\n\n${skill.body.trim()}\n\n<!-- dreamstate provenance: workspace=${provenance.workspaceId} issued=${new Date().toISOString()} skills=${provenance.indexVersion} content=${skillContentHash(skill)} -->\n`;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* A supporting page's path under its skill directory, or null when it is not a plain
|
|
173
|
+
* relative path of safe segments. A remote name becomes a path, so traversal and
|
|
174
|
+
* absolute paths are refused here rather than trusted.
|
|
175
|
+
*/
|
|
176
|
+
function supportingFilePath(directory, name) {
|
|
177
|
+
if (!name || name.startsWith('/') || name.includes('\\') || name.includes('\0'))
|
|
178
|
+
return null;
|
|
179
|
+
const segments = (0, node_path_1.normalize)(name).split(node_path_1.sep);
|
|
180
|
+
if (segments.some((segment) => !segment || segment === '..' || segment === '.' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(segment)))
|
|
181
|
+
return null;
|
|
182
|
+
if (segments.join('/') === 'SKILL.md')
|
|
183
|
+
return null;
|
|
184
|
+
return (0, node_path_1.join)(directory, ...segments);
|
|
133
185
|
}
|
|
134
186
|
function hash(value) { return (0, node_crypto_1.createHash)('sha256').update(value).digest('hex'); }
|
|
135
187
|
async function writeHostedSkill(root, skill, provenance) {
|
|
136
188
|
const name = skillName(skill.id);
|
|
137
189
|
const directory = (0, node_path_1.join)(root, name);
|
|
138
190
|
const markdown = renderSkillMarkdown(skill, provenance);
|
|
191
|
+
// A page removed upstream must not linger: clear a directory this CLI wrote before rewriting it.
|
|
192
|
+
if ((await (0, promises_1.readFile)((0, node_path_1.join)(directory, 'SKILL.md'), 'utf8').catch(() => '')).includes(PROVENANCE_MARKER)) {
|
|
193
|
+
await (0, promises_1.rm)(directory, { recursive: true, force: true });
|
|
194
|
+
}
|
|
139
195
|
await (0, promises_1.mkdir)(directory, { recursive: true, mode: 0o700 });
|
|
140
196
|
const file = (0, node_path_1.join)(directory, 'SKILL.md');
|
|
141
197
|
await (0, promises_1.writeFile)(file, markdown, { encoding: 'utf8', mode: 0o600 });
|
|
142
198
|
for (const document of skill.documents ?? []) {
|
|
143
|
-
|
|
199
|
+
const target = supportingFilePath(directory, document.name);
|
|
200
|
+
if (!target)
|
|
144
201
|
continue;
|
|
145
|
-
await (0, promises_1.
|
|
202
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(target), { recursive: true, mode: 0o700 });
|
|
203
|
+
await (0, promises_1.writeFile)(target, document.content, { encoding: 'utf8', mode: 0o600 });
|
|
146
204
|
}
|
|
147
205
|
return { file, name, computedHash: hash(markdown) };
|
|
148
206
|
}
|
|
@@ -151,8 +209,9 @@ async function linkAgentSkill(agentRoot, canonicalDirectory, name, copy) {
|
|
|
151
209
|
await (0, promises_1.mkdir)(agentRoot, { recursive: true, mode: 0o700 });
|
|
152
210
|
await (0, promises_1.rm)(targetDirectory, { recursive: true, force: true });
|
|
153
211
|
if (copy) {
|
|
154
|
-
|
|
155
|
-
|
|
212
|
+
// The whole skill directory, not only SKILL.md: the supporting pages the skill body
|
|
213
|
+
// links to must travel with it, or a copied skill points at files that are not there.
|
|
214
|
+
await (0, promises_1.cp)((0, node_path_1.join)(canonicalDirectory, name), targetDirectory, { recursive: true });
|
|
156
215
|
}
|
|
157
216
|
else {
|
|
158
217
|
await (0, promises_1.symlink)((0, node_path_1.relative)(agentRoot, (0, node_path_1.join)(canonicalDirectory, name)), targetDirectory, 'dir');
|
|
@@ -185,7 +244,7 @@ function routerSkill(packsList) {
|
|
|
185
244
|
const goals = records.map((item) => item && typeof item === 'object' ? item : {})
|
|
186
245
|
.filter((item) => typeof item.id === 'string');
|
|
187
246
|
const lines = goals.map((pack) => `- ${pack.id}: ${pack.owns ?? 'Dreamstate capability pack.'}${pack.not_for ? ` ${pack.not_for}` : ''}`).join('\n');
|
|
188
|
-
return { id: 'router', name: 'Dreamstate', description: 'Route work to Dreamstate capability skills.', body: `# Dreamstate router\n\nPrefer narrower \`dreamstate-*\` skill when one fits. Otherwise
|
|
247
|
+
return { id: 'router', name: 'Dreamstate', description: 'Route work to Dreamstate capability skills.', body: `# Dreamstate router\n\nPrefer a narrower \`dreamstate-*\` skill when one fits. Otherwise:\n\n${routerStepLines()}\n\n## Paid and outward-facing runs\n\n${agentGuidance_generated_json_1.default.consent.paid_run}\n\n${agentGuidance_generated_json_1.default.consent.human_approval}\n\n## Goals\n\n${lines || 'No packs returned by GET /packs.'}`, frontmatter: { packs: goals.map((pack) => pack.id) } };
|
|
189
248
|
}
|
|
190
249
|
async function inspectSkillInstallers(agents, homeDirectory = (0, node_os_1.homedir)(), project = false, cwd = process.cwd()) {
|
|
191
250
|
const canonical = canonicalSkillsDirectory(homeDirectory, project, cwd);
|
|
@@ -234,3 +293,43 @@ async function inspectSkillInstallers(agents, homeDirectory = (0, node_os_1.home
|
|
|
234
293
|
return { agent, directory, writable, canonical_directory: canonical, symlinks, provenance_age_ms };
|
|
235
294
|
}));
|
|
236
295
|
}
|
|
296
|
+
const PROVENANCE_PATTERN = /<!-- dreamstate provenance: workspace=[^\s]+ issued=[^\s]+ skills=[^\s]+(?: content=([0-9a-f]+))? -->/;
|
|
297
|
+
/**
|
|
298
|
+
* Compares what is installed with the live skills, by content stamp and not by age.
|
|
299
|
+
* An install an hour old that predates a server-side edit is stale; one from last
|
|
300
|
+
* month that nothing has changed since is current.
|
|
301
|
+
*/
|
|
302
|
+
async function compareInstalledSkills(live, homeDirectory = (0, node_os_1.homedir)(), project = false, cwd = process.cwd()) {
|
|
303
|
+
const canonical = canonicalSkillsDirectory(homeDirectory, project, cwd);
|
|
304
|
+
let names = [];
|
|
305
|
+
try {
|
|
306
|
+
names = (await (0, promises_1.readdir)(canonical)).filter((name) => name === 'dreamstate' || name.startsWith('dreamstate-'));
|
|
307
|
+
}
|
|
308
|
+
catch { /* nothing installed */ }
|
|
309
|
+
const installedStamps = new Map();
|
|
310
|
+
for (const name of names) {
|
|
311
|
+
const id = name === 'dreamstate' ? 'router' : name.slice('dreamstate-'.length);
|
|
312
|
+
try {
|
|
313
|
+
const markdown = await (0, promises_1.readFile)((0, node_path_1.join)(canonical, name, 'SKILL.md'), 'utf8');
|
|
314
|
+
installedStamps.set(id, PROVENANCE_PATTERN.exec(markdown)?.[1] ?? null);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
installedStamps.set(id, null);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const liveIds = live.map((skill) => skill.id);
|
|
321
|
+
const missing = liveIds.filter((id) => !installedStamps.has(id));
|
|
322
|
+
const extra = [...installedStamps.keys()].filter((id) => !liveIds.includes(id)).sort();
|
|
323
|
+
const stale = live
|
|
324
|
+
.filter((skill) => installedStamps.has(skill.id) && installedStamps.get(skill.id) !== skillContentHash(skill))
|
|
325
|
+
.map((skill) => ({ id: skill.id, installed: installedStamps.get(skill.id) ?? null, live: skillContentHash(skill) }));
|
|
326
|
+
return {
|
|
327
|
+
installed_skill_ids: [...installedStamps.keys()].sort(),
|
|
328
|
+
live_skill_ids: liveIds,
|
|
329
|
+
missing,
|
|
330
|
+
extra,
|
|
331
|
+
stale,
|
|
332
|
+
in_sync: missing.length === 0 && extra.length === 0 && stale.length === 0,
|
|
333
|
+
refresh_command: `dreamstate skills install${project ? ' --project' : ''}`,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
@@ -8,5 +8,19 @@ export interface LocalTableFilePreview {
|
|
|
8
8
|
writes: false;
|
|
9
9
|
}
|
|
10
10
|
export declare function inferLocalTableFileKind(filePath: string, format?: string): LocalTableFileKind;
|
|
11
|
+
export declare function parseCsv(text: string): {
|
|
12
|
+
rows: string[][];
|
|
13
|
+
endedClean: boolean;
|
|
14
|
+
unterminatedQuote: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The first worksheet of an xlsx file as rows of cell text. `rowLimit` stops at
|
|
18
|
+
* that many rows (the preview needs a header and a sample); null reads them all
|
|
19
|
+
* (an import needs every row).
|
|
20
|
+
*/
|
|
21
|
+
export declare function readXlsxRows(buffer: Buffer, rowLimit: number | null): {
|
|
22
|
+
rows: string[][];
|
|
23
|
+
totalRows: number;
|
|
24
|
+
};
|
|
11
25
|
export declare function previewLocalTableFile(filePath: string, format?: string): Promise<LocalTableFilePreview>;
|
|
12
26
|
//# sourceMappingURL=tableFilePreview.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tableFilePreview.d.ts","sourceRoot":"","sources":["../src/tableFilePreview.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,kBAAkB,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC;CACf;AAgBD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAK7F;
|
|
1
|
+
{"version":3,"file":"tableFilePreview.d.ts","sourceRoot":"","sources":["../src/tableFilePreview.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,kBAAkB,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC;CACf;AAgBD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAK7F;AAyBD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAAE,CAwC5G;AA0HD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAsC7G;AAQD,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAgB7G"}
|
package/dist/tableFilePreview.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.inferLocalTableFileKind = inferLocalTableFileKind;
|
|
4
|
+
exports.parseCsv = parseCsv;
|
|
5
|
+
exports.readXlsxRows = readXlsxRows;
|
|
4
6
|
exports.previewLocalTableFile = previewLocalTableFile;
|
|
5
7
|
const promises_1 = require("node:fs/promises");
|
|
6
8
|
const node_zlib_1 = require("node:zlib");
|
|
@@ -9,7 +11,7 @@ const TEXT_PREVIEW_BYTES = 1_000_000;
|
|
|
9
11
|
const XLSX_PREVIEW_BYTES = 20_000_000;
|
|
10
12
|
const MAPPABLE_FIELDS = [
|
|
11
13
|
'external_id', 'name', 'first_name', 'last_name', 'company',
|
|
12
|
-
'title', 'headline', 'email', 'linkedin_url', 'profile_url', 'avatar_url',
|
|
14
|
+
'title', 'headline', 'email', 'linkedin_url', 'profile_url', 'domain', 'avatar_url',
|
|
13
15
|
];
|
|
14
16
|
function normalizeHeaderKey(value) {
|
|
15
17
|
return value.trim().toLowerCase().replace(/[\s_-]+/g, '');
|
|
@@ -101,7 +103,7 @@ function parseCsv(text) {
|
|
|
101
103
|
if (!(row.length === 1 && row[0] === ''))
|
|
102
104
|
rows.push(row);
|
|
103
105
|
}
|
|
104
|
-
return { rows, endedClean };
|
|
106
|
+
return { rows, endedClean, unterminatedQuote: quoted };
|
|
105
107
|
}
|
|
106
108
|
function previewDelimited(kind, text, clipped) {
|
|
107
109
|
const parsed = parseCsv(text);
|
|
@@ -234,13 +236,18 @@ function sharedStringTable(xml) {
|
|
|
234
236
|
return [...xml.matchAll(/<(?:[\w-]+:)?si\b[^>]*>([\s\S]*?)<\/(?:[\w-]+:)?si>/g)]
|
|
235
237
|
.map((match) => xmlTexts(match[1] ?? ''));
|
|
236
238
|
}
|
|
237
|
-
|
|
239
|
+
/**
|
|
240
|
+
* The first worksheet of an xlsx file as rows of cell text. `rowLimit` stops at
|
|
241
|
+
* that many rows (the preview needs a header and a sample); null reads them all
|
|
242
|
+
* (an import needs every row).
|
|
243
|
+
*/
|
|
244
|
+
function readXlsxRows(buffer, rowLimit) {
|
|
238
245
|
const entries = unzipEntries(buffer);
|
|
239
246
|
const sheetName = [...entries.keys()]
|
|
240
247
|
.filter((name) => /xl\/worksheets\/sheet\d+\.xml$/.test(name))
|
|
241
248
|
.sort()[0];
|
|
242
249
|
if (!sheetName)
|
|
243
|
-
throw new Error('xlsx
|
|
250
|
+
throw new Error('xlsx file has no worksheet');
|
|
244
251
|
const sheet = entries.get(sheetName).toString('utf8');
|
|
245
252
|
const sharedName = [...entries.keys()].find((name) => name.endsWith('xl/sharedStrings.xml'));
|
|
246
253
|
const shared = sharedName ? sharedStringTable(entries.get(sharedName).toString('utf8')) : [];
|
|
@@ -248,7 +255,7 @@ function previewXlsx(buffer) {
|
|
|
248
255
|
for (const rowMatch of sheet.matchAll(/<(?:[\w-]+:)?row\b([^>]*)>([\s\S]*?)<\/(?:[\w-]+:)?row>/g)) {
|
|
249
256
|
const rowRef = /\br="(\d+)"/.exec(rowMatch[1] ?? '');
|
|
250
257
|
const rowNumber = rowRef ? Number(rowRef[1]) : grid.size + 1;
|
|
251
|
-
if (rowNumber >
|
|
258
|
+
if (rowLimit !== null && rowNumber > rowLimit)
|
|
252
259
|
continue;
|
|
253
260
|
const cells = new Map();
|
|
254
261
|
for (const cellMatch of (rowMatch[2] ?? '').matchAll(/<(?:[\w-]+:)?c\b([^>]*)(?:\/>|>([\s\S]*?)<\/(?:[\w-]+:)?c>)/g)) {
|
|
@@ -277,9 +284,13 @@ function previewXlsx(buffer) {
|
|
|
277
284
|
throw new Error('File has no header row');
|
|
278
285
|
const width = Math.max(...[...grid.values()].map((cells) => Math.max(-1, ...cells.keys()))) + 1;
|
|
279
286
|
const toRow = (cells) => Array.from({ length: width }, (_, index) => cells?.get(index) ?? '');
|
|
280
|
-
const [
|
|
281
|
-
|
|
282
|
-
|
|
287
|
+
const totalRows = [...sheet.matchAll(/<(?:[\w-]+:)?row\b/g)].length;
|
|
288
|
+
return { rows: rowNumbers.map((number) => toRow(grid.get(number))), totalRows };
|
|
289
|
+
}
|
|
290
|
+
function previewXlsx(buffer) {
|
|
291
|
+
const { rows, totalRows } = readXlsxRows(buffer, SAMPLE_ROWS + 1);
|
|
292
|
+
const [header, ...dataRows] = rows;
|
|
293
|
+
return finish('xlsx', header ?? [], dataRows, totalRows > SAMPLE_ROWS + 1);
|
|
283
294
|
}
|
|
284
295
|
async function previewLocalTableFile(filePath, format) {
|
|
285
296
|
const kind = inferLocalTableFileKind(filePath, format);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type LocalTableFileKind } from './tableFilePreview';
|
|
2
|
+
/** One staged row, the wire shape of tables.table_sources.csv_upload_stage.rows. */
|
|
3
|
+
export interface StagedRow {
|
|
4
|
+
source_row_number: number;
|
|
5
|
+
data: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export interface LocalTableFile {
|
|
8
|
+
file_kind: LocalTableFileKind;
|
|
9
|
+
headers: string[];
|
|
10
|
+
rows: StagedRow[];
|
|
11
|
+
}
|
|
12
|
+
/** Largest file an import reads into memory. The server caps one upload at 200,000 rows. */
|
|
13
|
+
export declare const MAX_IMPORT_FILE_BYTES = 250000000;
|
|
14
|
+
/**
|
|
15
|
+
* Duplicate CSV labels cannot be object keys twice, so they get private
|
|
16
|
+
* staging keys; the column definitions keep the labels. Same rule as
|
|
17
|
+
* csvStagingHeaderKeys in packages/types (the server's own parser), pinned by
|
|
18
|
+
* tableFileReader.test.ts against that function.
|
|
19
|
+
*/
|
|
20
|
+
export declare function stagingHeaderKeys(headers: readonly string[]): string[];
|
|
21
|
+
/** Every row of a local CSV, JSON, JSONL or XLSX file, shaped for csv_upload_stage. */
|
|
22
|
+
export declare function readLocalTableFile(filePath: string, format?: string): Promise<{
|
|
23
|
+
file: LocalTableFile;
|
|
24
|
+
bytes: Buffer;
|
|
25
|
+
}>;
|
|
26
|
+
//# sourceMappingURL=tableFileReader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tableFileReader.d.ts","sourceRoot":"","sources":["../src/tableFileReader.ts"],"names":[],"mappings":"AACA,OAAO,EAAmD,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE9G,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,kBAAkB,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAED,4FAA4F;AAC5F,eAAO,MAAM,qBAAqB,YAAc,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWtE;AAyCD,uFAAuF;AACvF,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAwB5H"}
|