feat(cli+skill): make documented field UX real + fix skill doc drift

CLI (packages/inkreach-cli):
- new src/lib/field-normalize.ts: type aliases (sel/num/text/mul/…),
  select string-choices -> {name}, compact --fields 'Name:type:choices'
  expansion, smart type inference from --options shape / --name
  (--type now optional), name->ID resolution for foreignTableName /
  linkFieldName / lookupFieldName (incl. link-field foreignTableId
  backfill + unknown-key stripping), rating/number/button required-
  option defaults
- field create: +--description/--lookup-options/--is-lookup
- field update --options + table create --fields route through the
  normalizer; DEFAULT_ENDPOINT :3000 -> :3003
- 20 unit tests (field-normalize.spec.ts, vitest); live-verified:
  string choices, alias, foreignTableName, lookup-by-names, compact
  format, inference (priority->singleSelect, max:5->rating)

Skill docs (.agents/skills/inkreach-teable):
- ports :3000 -> :3003 everywhere (backend real port; :3000 is an
  unrelated container on this host)
- remove dead hr_admin account; note throwaway signup + real admin
- plugin register: actual URL-based flags; document plugin csp
  list/set; view/dashboard install requires config enable first
- field-types.md marked verified-2026-08 (aliases/inference/name
  resolution now implemented + tested)
- development.md: webpack dist/index.js cache trap; PGPASSWORD
  placeholder instead of wrong literal
This commit is contained in:
yeuimu
2026-08-16 05:58:01 +00:00
parent 5a984eb5f8
commit e1a91dfa84
13 changed files with 1015 additions and 34 deletions
+40 -5
View File
@@ -9,6 +9,7 @@ exports.baseResourceCommands = void 0;
const commander_1 = require("commander");
const ctx_1 = require("../lib/ctx");
const output_1 = require("../lib/output");
const field_normalize_1 = require("../lib/field-normalize");
function baseResourceCommands() {
return [baseCmd(), tableCmd(), fieldCmd(), recordCmd(), viewCmd()];
}
@@ -103,8 +104,20 @@ function tableCmd() {
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const body = { name: opts.name };
if (opts.fields)
body.fields = JSON.parse(opts.fields);
if (opts.fields) {
body.fields = (0, field_normalize_1.normalizeFieldRoArray)(JSON.parse(opts.fields));
// Resolve foreignTableName references against existing tables in the base.
const fields = body.fields;
const tables = await ctx.client.listTables(baseId).catch(() => []);
for (const f of fields) {
const options = f?.options;
if (options && typeof options.foreignTableName === 'string' && !options.foreignTableId) {
const t = (0, field_normalize_1.findTableByNameOrId)(tables || [], options.foreignTableName);
if (t)
options.foreignTableId = t.id;
}
}
}
const table = await ctx.client.createTable(baseId, body);
if (ctx.json)
(0, output_1.printJson)(table);
@@ -188,13 +201,34 @@ function fieldCmd() {
.description('Create a new field')
.requiredOption('--table-id <id>', 'Table id')
.requiredOption('--name <name>', 'Field name')
.requiredOption('--type <type>', 'Field type (singleLineText, number, singleSelect, link, formula, etc.)')
.option('--options <json>', 'Field options JSON (type-specific)')
.option('--type <type>', 'Field type — full value (singleLineText, singleSelect, link…) or alias (text, sel, num…). Omitted: inferred from --options shape / --name')
.option('--options <json>', 'Field options JSON (type-specific; select choices accept plain strings)')
.option('--description <d>', 'Field description')
.option('--lookup-options <json>', 'Lookup options JSON (names like linkFieldName/lookupFieldName are resolved to IDs)')
.option('--is-lookup', 'Mark the field as a lookup field')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const body = { name: opts.name, type: opts.type };
const body = { name: opts.name };
if (opts.options)
body.options = JSON.parse(opts.options);
if (opts.description)
body.description = opts.description;
if (opts.lookupOptions)
body.lookupOptions = JSON.parse(opts.lookupOptions);
if (opts.isLookup)
body.isLookup = true;
body.type = opts.type
? (0, field_normalize_1.resolveFieldType)(opts.type)
: (0, field_normalize_1.inferFieldType)(opts.name, body.options);
(0, field_normalize_1.normalizeFieldRo)(body);
const errors = await (0, field_normalize_1.resolveFieldReferences)(body, {
listTables: (baseId) => ctx.client.listTables(baseId),
listFields: (tableId) => ctx.client.listFields(tableId),
baseId: ctx.baseId,
tableId: opts.tableId,
});
if (errors.length)
throw new Error(errors.join('; '));
const field = await ctx.client.createField(opts.tableId, body);
if (ctx.json)
(0, output_1.printJson)(field);
@@ -235,6 +269,7 @@ function fieldCmd() {
};
if (opts.name)
body.name = opts.name;
(0, field_normalize_1.normalizeFieldRo)(body);
const field = await ctx.client.convertField(tableId, fieldId, body);
if (ctx.json)
(0, output_1.printJson)(field);