code wiki / _hdl_build / nx_vizsla_import.nx

nx_vizsla_import.nx source

↩ module page · 530 lines · 21143 B

1// nx_vizsla_import.nx -- NISHI VIZSLA: import your existing relationships into a tenant. Operator 2026-06-22: 2// "i want to import my linkedin my email my phone etc i may be forgetting things." These all export in OPEN 3// standard formats that we parse SOVEREIGNLY (no API, no cloud, your data never leaves the machine -- the whole 4// Vizsla differentiator): 5// vcard <base> <tid> <file.vcf> phone / email / iCloud / Google / Outlook contacts (RFC 6350 vCard) 6// linkedin <base> <tid> <conn.csv> LinkedIn "Connections.csv" (Settings->Data Privacy->Get a copy of your data) 7// Each parsed person -> a CONTACT line in the tenant's contacts.txt (default tier "network" -- you promote the 8// close ones); LinkedIn's "Connected On" date -> a TOUCH so imported pros aren't all flagged NEW. DEDUP vs the 9// tenant's EXISTING contact ids (read first) => re-importing is idempotent/safe. Name -> a stable handle 10// ([A-Za-z0-9-] lowercased). If the tenant has no contacts file yet, a default CONF+TIERs header is written. 11// Composes per-tenant like nx_vizsla_tenant; forks the blessed engine only to re-load LinkedIn touches. 12// license_tier: ORIGINAL expect_exit: 1 (no args) 13import "nx_syscalls.nx" 14const K_MAGIC_65536: i64 = 65536 15const K_MAGIC_1024: i64 = 1024 16 17func vi_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 18func vi_p(s: *u8) -> i64 { sys_write(1, s, vi_slen(s)); return 0 } 19 20func vi_eq(a: *u8, b: *u8) -> i64 { 21 var i: i64 = 0 22 var go: i64 = 1 23 while go == 1 { 24 if a[i] != b[i] { return 0 } 25 if a[i] == (0 as u8) { return 1 } 26 i = i + 1 27 } 28 return 0 29} 30 31func vi_cat(dst: *u8, off: i64, s: *u8) -> i64 { 32 var i: i64 = 0 33 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 } 34 return off + i 35} 36 37func vi_catn(dst: *u8, off: i64, v: i64) -> i64 { 38 var o: i64 = off 39 var m: i64 = v 40 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m } 41 let t: *u8 = sys_mmap(28) 42 var k: i64 = 0 43 if m == 0 { t[0] = 48 as u8; k = 1 } 44 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 45 var i: i64 = 0 46 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 } 47 return o + k 48} 49 50func vi_dup(s: *u8) -> *u8 { 51 let n: i64 = vi_slen(s) 52 let d: *u8 = sys_mmap(n + 2) 53 var i: i64 = 0 54 while i <= n { d[i] = s[i]; i = i + 1 } 55 return d 56} 57 58func vi_atoi(s: *u8) -> i64 { 59 var v: i64 = 0 60 var i: i64 = 0 61 while s[i] != (0 as u8) { let d: i64 = (s[i] as i64) - 48; if d >= 0 { if d <= 9 { v = v * 10 + d } } i = i + 1 } 62 return v 63} 64 65// next whitespace-delimited word of s from offset off -> out; returns new offset 66func vi_word(s: *u8, off: i64, out: *u8, cap: i64) -> i64 { 67 var p: i64 = off 68 while s[p] == (32 as u8) { p = p + 1 } 69 var o: i64 = 0 70 while s[p] != (0 as u8) { 71 if s[p] == (32 as u8) { break } 72 if o < cap - 1 { out[o] = s[p]; o = o + 1 } 73 p = p + 1 74 } 75 out[o] = 0 as u8 76 return p 77} 78 79func vi_find(list: *i64, n: i64, s: *u8) -> i64 { 80 var i: i64 = 0 81 while i < n { if vi_eq(list[i] as *u8, s) == 1 { return i } i = i + 1 } 82 return 0 - 1 83} 84 85func vi_readall(path: *u8, szp: *i64) -> *u8 { 86 let fd: i64 = sys_openat_rd(path) 87 if fd < 0 { szp[0] = 0; return 0 as *u8 } 88 let sz: i64 = sys_lseek(fd, 0, 2) 89 sys_lseek(fd, 0, 0) 90 let buf: *u8 = sys_mmap(sz + 64) 91 var got: i64 = 0 92 var n: i64 = 1 93 while n > 0 { n = sys_read(fd, (buf as i64 + got) as *u8, K_MAGIC_65536); if n > 0 { got = got + n } } 94 sys_close(fd) 95 szp[0] = got 96 return buf 97} 98 99// upper bound on the number of lines in `path` (newlines + 1 for a possibly-unterminated final line); 0 if 100// unreadable. Used to size the dedup set DATA-DRIVEN so no fixed cap can silently drop ids. 101func vi_count_lines(path: *u8) -> i64 { 102 let szp: *i64 = sys_mmap(16) as *i64 103 let b: *u8 = vi_readall(path, szp) 104 let sz: i64 = szp[0] 105 if sz <= 0 { return 0 } 106 var nl: i64 = 0 107 var i: i64 = 0 108 while i < sz { if b[i] == (10 as u8) { nl = nl + 1 } i = i + 1 } 109 return nl + 1 110} 111 112func vi_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 } 113 114func vi_writefile(path: *u8, content: *u8) -> i64 { 115 let fd: i64 = sys_openat_wr(path, 0x1a4) 116 if fd < 0 { return 0 - 1 } 117 sys_write(fd, content, vi_slen(content)) 118 sys_close(fd) 119 return 0 120} 121 122func vi_append(path: *u8, buf: *u8, n: i64) -> i64 { 123 let fd: i64 = sys_openat_append(path, 0x1a4) 124 if fd < 0 { return 0 - 1 } 125 sys_write(fd, buf, n) 126 sys_close(fd) 127 return 0 128} 129 130func vi_tpath(out: *u8, base: *u8, tid: *u8, suffix: *u8) -> i64 { 131 var o: i64 = 0 132 o = vi_cat(out, o, base); o = vi_cat(out, o, tid); o = vi_cat(out, o, suffix) 133 out[o] = 0 as u8 134 return o 135} 136 137// "John Smith" -> disp "John-Smith" (preserve case, separators->'-') + id "john-smith" (lowercased); ret len (0=empty) 138func vi_sanitize(src: *u8, disp: *u8, id: *u8) -> i64 { 139 var i: i64 = 0 140 var o: i64 = 0 141 var lastsep: i64 = 1 142 while src[i] != (0 as u8) { 143 let c: i64 = src[i] as i64 144 var isaln: i64 = 0 145 if c >= 48 { if c <= 57 { isaln = 1 } } 146 if c >= 65 { if c <= 90 { isaln = 1 } } 147 if c >= 97 { if c <= 122 { isaln = 1 } } 148 if isaln == 1 { 149 disp[o] = c as u8 150 var lc: i64 = c 151 if c >= 65 { if c <= 90 { lc = c + 32 } } 152 id[o] = lc as u8 153 o = o + 1 154 lastsep = 0 155 } else { 156 if lastsep == 0 { disp[o] = 45 as u8; id[o] = 45 as u8; o = o + 1; lastsep = 1 } 157 } 158 i = i + 1 159 } 160 if o > 0 { if disp[o - 1] == (45 as u8) { o = o - 1 } } 161 disp[o] = 0 as u8 162 id[o] = 0 as u8 163 return o 164} 165 166// 3-letter English month -> 1..12 (else 0) 167func vi_mon3(s: *u8) -> i64 { 168 if vi_eq(s, "Jan" as *u8) == 1 { return 1 } 169 if vi_eq(s, "Feb" as *u8) == 1 { return 2 } 170 if vi_eq(s, "Mar" as *u8) == 1 { return 3 } 171 if vi_eq(s, "Apr" as *u8) == 1 { return 4 } 172 if vi_eq(s, "May" as *u8) == 1 { return 5 } 173 if vi_eq(s, "Jun" as *u8) == 1 { return 6 } 174 if vi_eq(s, "Jul" as *u8) == 1 { return 7 } 175 if vi_eq(s, "Aug" as *u8) == 1 { return 8 } 176 if vi_eq(s, "Sep" as *u8) == 1 { return 9 } 177 if vi_eq(s, "Oct" as *u8) == 1 { return 10 } 178 if vi_eq(s, "Nov" as *u8) == 1 { return 11 } 179 if vi_eq(s, "Dec" as *u8) == 1 { return 12 } 180 return 0 181} 182 183// vCard BDAY value (e.g. "1985-07-03", "--0703", "19850703") -> "MM-DD" via last two digit-groups; ret 1 if ok 184func vi_bday_mmdd(src: *u8, out: *u8) -> i64 { 185 let g: *i64 = sys_mmap(8 * 8) as *i64 186 var ng: i64 = 0 187 var cur: i64 = 0 188 var indig: i64 = 0 189 var i: i64 = 0 190 var go: i64 = 1 191 while go == 1 { 192 let c: i64 = src[i] as i64 193 var isd: i64 = 0 194 if c >= 48 { if c <= 57 { isd = 1 } } 195 if isd == 1 { cur = cur * 10 + (c - 48); indig = 1 } 196 if isd == 0 { if indig == 1 { if ng < 8 { g[ng] = cur; ng = ng + 1 } cur = 0; indig = 0 } } 197 if c == 0 { go = 0 } 198 i = i + 1 199 } 200 if ng < 2 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 } 201 let mm: i64 = g[ng - 2] 202 let dd: i64 = g[ng - 1] 203 if mm < 1 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 } 204 if mm > 12 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 } 205 if dd < 1 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 } 206 if dd > 31 { out[0] = 45 as u8; out[1] = 0 as u8; return 0 } 207 var o: i64 = 0 208 if mm < 10 { out[o] = 48 as u8; o = o + 1 } 209 o = vi_catn(out, o, mm) 210 out[o] = 45 as u8; o = o + 1 211 if dd < 10 { out[o] = 48 as u8; o = o + 1 } 212 o = vi_catn(out, o, dd) 213 out[o] = 0 as u8 214 return 1 215} 216 217// line [0,llen) begins with "field" then ':' or ';' ? 218func vi_field_is(line: *u8, llen: i64, field: *u8) -> i64 { 219 let fl: i64 = vi_slen(field) 220 if fl >= llen { return 0 } 221 var j: i64 = 0 222 while j < fl { if line[j] != field[j] { return 0 } j = j + 1 } 223 let nx: i64 = line[fl] as i64 224 if nx == 58 { return 1 } 225 if nx == 59 { return 1 } 226 return 0 227} 228 229// value after the first ':' in line[0,llen) 230func vi_value(line: *u8, llen: i64, out: *u8, cap: i64) -> i64 { 231 var p: i64 = 0 232 while p < llen { if line[p] == (58 as u8) { break } p = p + 1 } 233 if p >= llen { out[0] = 0 as u8; return 0 } 234 p = p + 1 235 var o: i64 = 0 236 while p < llen { if o < cap - 1 { out[o] = line[p]; o = o + 1 } p = p + 1 } 237 out[o] = 0 as u8 238 return o 239} 240 241// k-th comma field of line[0,llen) -> out (k from 0) 242func vi_csv_field(line: *u8, llen: i64, k: i64, out: *u8, cap: i64) -> i64 { 243 var idx: i64 = 0 244 var p: i64 = 0 245 while idx < k { 246 if p >= llen { out[0] = 0 as u8; return 0 } 247 if line[p] == (44 as u8) { idx = idx + 1 } 248 p = p + 1 249 } 250 var o: i64 = 0 251 var go: i64 = 1 252 while go == 1 { 253 if p >= llen { go = 0 } else { 254 if line[p] == (44 as u8) { go = 0 } else { 255 if o < cap - 1 { out[o] = line[p]; o = o + 1 } 256 p = p + 1 257 } 258 } 259 } 260 out[o] = 0 as u8 261 return o 262} 263 264// fill seen[] with the tenant's existing CONTACT ids (so re-import is idempotent); returns count 265func vi_existing_ids(cpath: *u8, seen: *i64, ns0: i64, cap: i64) -> i64 { 266 let szp: *i64 = sys_mmap(16) as *i64 267 let b: *u8 = vi_readall(cpath, szp) 268 let sz: i64 = szp[0] 269 var ns: i64 = ns0 270 if sz <= 0 { return ns } 271 let tok: *u8 = sys_mmap(256) 272 var i: i64 = 0 273 while i < sz { 274 var e: i64 = i 275 var go: i64 = 1 276 while go == 1 { if e >= sz { go = 0 } else { if b[e] == (10 as u8) { go = 0 } else { e = e + 1 } } } 277 var le: i64 = e 278 if le > i { if b[le - 1] == (13 as u8) { le = le - 1 } } 279 let llen: i64 = le - i 280 let line: *u8 = (b as i64 + i) as *u8 281 if vi_field_is(line, llen, "CONTACT" as *u8) == 0 { 282 // not "CONTACT:" form -- CONTACT lines are space-delimited, so check prefix "CONTACT " 283 if llen > 8 { 284 if line[0] == (67 as u8) { if line[7] == (32 as u8) { 285 var p: i64 = 8 286 var o: i64 = 0 287 while p < llen { if line[p] == (32 as u8) { break } if o < 255 { tok[o] = line[p]; o = o + 1 } p = p + 1 } 288 tok[o] = 0 as u8 289 if o > 0 { if vi_find(seen, ns, tok) < 0 { if ns < cap { seen[ns] = vi_dup(tok) as i64; ns = ns + 1 } } } 290 } } 291 } 292 } 293 i = e + 1 294 } 295 return ns 296} 297 298func vi_ensure_contacts(cpath: *u8, lpath: *u8) -> i64 { 299 if vi_exists(cpath) == 0 { vi_writefile(cpath, "CONF soon_days 14\nTIER inner 14\nTIER close 45\nTIER network 120\n" as *u8) } 300 if vi_exists(lpath) == 0 { vi_writefile(lpath, "# interaction journal (imported + manual)\n" as *u8) } 301 return 0 302} 303 304func vi_emit_contact(cpath: *u8, id: *u8, disp: *u8, bday: *u8) -> i64 { 305 let buf: *u8 = sys_mmap(K_MAGIC_1024) 306 var o: i64 = 0 307 o = vi_cat(buf, o, "CONTACT " as *u8); o = vi_cat(buf, o, id); buf[o] = 32 as u8; o = o + 1 308 o = vi_cat(buf, o, disp); buf[o] = 32 as u8; o = o + 1 309 o = vi_cat(buf, o, "network " as *u8); o = vi_cat(buf, o, bday); buf[o] = 10 as u8; o = o + 1 310 return vi_append(cpath, buf, o) 311} 312 313func vi_emit_touch(lpath: *u8, date: *u8, id: *u8) -> i64 { 314 let buf: *u8 = sys_mmap(K_MAGIC_1024) 315 var o: i64 = 0 316 o = vi_cat(buf, o, "TOUCH " as *u8); o = vi_cat(buf, o, date); buf[o] = 32 as u8; o = o + 1 317 o = vi_cat(buf, o, id); o = vi_cat(buf, o, " connected-on-linkedin\n" as *u8) 318 return vi_append(lpath, buf, o) 319} 320 321// fork+exec the blessed relate engine "load <log> <prefix>" (re-ingest imported touches) 322func vi_reload(logp: *u8, prefix: *u8) -> i64 { 323 let pid: i64 = sys_fork() 324 if pid == 0 { 325 let argv: *i64 = sys_mmap(64) as *i64 326 argv[0] = "_offc/nx_vizsla_relate.elf" as *u8 as i64 327 argv[1] = "load" as *u8 as i64 328 argv[2] = logp as i64 329 argv[3] = prefix as i64 330 argv[4] = 0 331 let envp: *i64 = sys_mmap(16) as *i64 332 envp[0] = 0 333 sys_execve("_offc/nx_vizsla_relate.elf" as *u8, argv, envp) 334 sys_exit(127) 335 } 336 let st: *i64 = sys_mmap(16) as *i64 337 sys_wait4(pid, st, 0) 338 return (st[0] >> 8) & 0xff 339} 340 341// ---------------- VCARD ---------------- 342func vi_cmd_vcard(argc: i64, argv: *i64) -> i64 { 343 if argc < 5 { vi_p("VIZSLA-IMPORT vcard needs <base> <tid> <file.vcf> -- fail loud\n" as *u8); return 1 } 344 let base: *u8 = argv[2] as *u8 345 let tid: *u8 = argv[3] as *u8 346 let vpath: *u8 = argv[4] as *u8 347 let cpath: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(cpath, base, tid, ".contacts.txt" as *u8) 348 let lpath: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(lpath, base, tid, ".relate_log.txt" as *u8) 349 vi_ensure_contacts(cpath, lpath) 350 351 // DATA-DRIVEN dedup-set size: seen[] holds at most one id per line across the tenant's existing contacts 352 // + the import source, so size it from their actual line counts. No fixed cap -> re-import stays idempotent 353 // at any scale (this was the janitor-caps silent-truncation smell: a 4096 cap dropped ids past 4096 and 354 // re-emitted them as duplicates every re-import). Eaten here, data-driven. 355 let cap: i64 = vi_count_lines(cpath) + vi_count_lines(vpath) 356 let seen: *i64 = sys_mmap(8 * cap) as *i64 357 var ns: i64 = vi_existing_ids(cpath, seen, 0, cap) 358 359 let szp: *i64 = sys_mmap(16) as *i64 360 let b: *u8 = vi_readall(vpath, szp) 361 let sz: i64 = szp[0] 362 if sz <= 0 { vi_p("VIZSLA-IMPORT vcf MISSING/EMPTY -- fail loud\n" as *u8); return 1 } 363 364 let tmp: *u8 = sys_mmap(512) 365 let disp: *u8 = sys_mmap(256) 366 let id: *u8 = sys_mmap(256) 367 let bday: *u8 = sys_mmap(32) 368 var have_fn: i64 = 0 369 var have_bd: i64 = 0 370 var imported: i64 = 0 371 var skipped: i64 = 0 372 var i: i64 = 0 373 while i < sz { 374 var e: i64 = i 375 var go: i64 = 1 376 while go == 1 { if e >= sz { go = 0 } else { if b[e] == (10 as u8) { go = 0 } else { e = e + 1 } } } 377 var le: i64 = e 378 if le > i { if b[le - 1] == (13 as u8) { le = le - 1 } } 379 let llen: i64 = le - i 380 let line: *u8 = (b as i64 + i) as *u8 381 if vi_field_is(line, llen, "BEGIN" as *u8) == 1 { have_fn = 0; have_bd = 0 } 382 if vi_field_is(line, llen, "FN" as *u8) == 1 { 383 vi_value(line, llen, tmp, 512) 384 if vi_sanitize(tmp, disp, id) > 0 { have_fn = 1 } 385 } 386 if vi_field_is(line, llen, "BDAY" as *u8) == 1 { 387 vi_value(line, llen, tmp, 512) 388 have_bd = vi_bday_mmdd(tmp, bday) 389 } 390 if vi_field_is(line, llen, "END" as *u8) == 1 { 391 if have_fn == 1 { 392 if vi_find(seen, ns, id) < 0 { 393 var bd: *u8 = "-" as *u8 394 if have_bd == 1 { bd = bday } 395 vi_emit_contact(cpath, id, disp, bd) 396 if ns < cap { seen[ns] = vi_dup(id) as i64; ns = ns + 1 } 397 imported = imported + 1 398 } else { skipped = skipped + 1 } 399 } 400 have_fn = 0; have_bd = 0 401 } 402 i = e + 1 403 } 404 405 let rep: *u8 = sys_mmap(256) 406 var o: i64 = 0 407 o = vi_cat(rep, o, "VIZSLA-IMPORT-VCARD tenant=" as *u8); o = vi_cat(rep, o, tid) 408 o = vi_cat(rep, o, " imported=" as *u8); o = vi_catn(rep, o, imported) 409 o = vi_cat(rep, o, " skipped=" as *u8); o = vi_catn(rep, o, skipped) 410 o = vi_cat(rep, o, " (review " as *u8); o = vi_cat(rep, o, cpath); o = vi_cat(rep, o, ", promote close ones from network)\n" as *u8) 411 sys_write(1, rep, o) 412 return 0 413} 414 415// ---------------- LINKEDIN ---------------- 416func vi_cmd_linkedin(argc: i64, argv: *i64) -> i64 { 417 if argc < 5 { vi_p("VIZSLA-IMPORT linkedin needs <base> <tid> <Connections.csv> -- fail loud\n" as *u8); return 1 } 418 let base: *u8 = argv[2] as *u8 419 let tid: *u8 = argv[3] as *u8 420 let cpath0: *u8 = argv[4] as *u8 421 let cpath: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(cpath, base, tid, ".contacts.txt" as *u8) 422 let lpath: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(lpath, base, tid, ".relate_log.txt" as *u8) 423 let prefix: *u8 = sys_mmap(K_MAGIC_1024); vi_tpath(prefix, base, tid, ".relate-" as *u8) 424 vi_ensure_contacts(cpath, lpath) 425 426 // DATA-DRIVEN dedup-set size (see vi_cmd_vcard): from existing-contacts + source line counts, no fixed cap. 427 let cap: i64 = vi_count_lines(cpath) + vi_count_lines(cpath0) 428 let seen: *i64 = sys_mmap(8 * cap) as *i64 429 var ns: i64 = vi_existing_ids(cpath, seen, 0, cap) 430 431 let szp: *i64 = sys_mmap(16) as *i64 432 let b: *u8 = vi_readall(cpath0, szp) 433 let sz: i64 = szp[0] 434 if sz <= 0 { vi_p("VIZSLA-IMPORT csv MISSING/EMPTY -- fail loud\n" as *u8); return 1 } 435 436 // locate header columns by name (robust to column order) 437 var i_first: i64 = 0 - 1 438 var i_last: i64 = 0 - 1 439 var i_conn: i64 = 0 - 1 440 let fld: *u8 = sys_mmap(256) 441 let first: *u8 = sys_mmap(256) 442 let last: *u8 = sys_mmap(256) 443 let conn: *u8 = sys_mmap(256) 444 let nm: *u8 = sys_mmap(512) 445 let disp: *u8 = sys_mmap(256) 446 let id: *u8 = sys_mmap(256) 447 let mon: *u8 = sys_mmap(16) 448 let dnum: *u8 = sys_mmap(16) 449 let ynum: *u8 = sys_mmap(16) 450 let date: *u8 = sys_mmap(32) 451 var imported: i64 = 0 452 var skipped: i64 = 0 453 var i: i64 = 0 454 while i < sz { 455 var e: i64 = i 456 var go: i64 = 1 457 while go == 1 { if e >= sz { go = 0 } else { if b[e] == (10 as u8) { go = 0 } else { e = e + 1 } } } 458 var le: i64 = e 459 if le > i { if b[le - 1] == (13 as u8) { le = le - 1 } } 460 let llen: i64 = le - i 461 let line: *u8 = (b as i64 + i) as *u8 462 if i_first < 0 { 463 // still seeking the header row (the one whose first field is "First Name") 464 vi_csv_field(line, llen, 0, fld, 256) 465 if vi_eq(fld, "First Name" as *u8) == 1 { 466 var k: i64 = 0 467 while k < 16 { 468 let got: i64 = vi_csv_field(line, llen, k, fld, 256) 469 if vi_eq(fld, "First Name" as *u8) == 1 { i_first = k } 470 if vi_eq(fld, "Last Name" as *u8) == 1 { i_last = k } 471 if vi_eq(fld, "Connected On" as *u8) == 1 { i_conn = k } 472 if got == 0 { k = 16 } else { k = k + 1 } 473 } 474 } 475 } else { 476 vi_csv_field(line, llen, i_first, first, 256) 477 vi_csv_field(line, llen, i_last, last, 256) 478 var no: i64 = 0 479 no = vi_cat(nm, no, first); nm[no] = 32 as u8; no = no + 1; no = vi_cat(nm, no, last); nm[no] = 0 as u8 480 if vi_sanitize(nm, disp, id) > 0 { 481 if vi_find(seen, ns, id) < 0 { 482 vi_emit_contact(cpath, id, disp, "-" as *u8) 483 if ns < cap { seen[ns] = vi_dup(id) as i64; ns = ns + 1 } 484 imported = imported + 1 485 // "Connected On" e.g. "15 Jun 2026" -> TOUCH 2026-06-15 486 if i_conn >= 0 { 487 vi_csv_field(line, llen, i_conn, conn, 256) 488 var ci: i64 = 0 489 ci = vi_word(conn, ci, dnum, 16) 490 ci = vi_word(conn, ci, mon, 16) 491 ci = vi_word(conn, ci, ynum, 16) 492 let mn: i64 = vi_mon3(mon) 493 if mn > 0 { 494 var do2: i64 = 0 495 do2 = vi_cat(date, do2, ynum); date[do2] = 45 as u8; do2 = do2 + 1 496 if mn < 10 { date[do2] = 48 as u8; do2 = do2 + 1 } 497 do2 = vi_catn(date, do2, mn); date[do2] = 45 as u8; do2 = do2 + 1 498 let dv: i64 = vi_atoi(dnum) 499 if dv < 10 { date[do2] = 48 as u8; do2 = do2 + 1 } 500 do2 = vi_catn(date, do2, dv); date[do2] = 0 as u8 501 vi_emit_touch(lpath, date, id) 502 } 503 } 504 } else { skipped = skipped + 1 } 505 } 506 } 507 i = e + 1 508 } 509 if i_first < 0 { vi_p("VIZSLA-IMPORT linkedin: no 'First Name' header found -- is this a LinkedIn Connections.csv? -- fail loud\n" as *u8); return 1 } 510 511 vi_reload(lpath, prefix) 512 513 let rep: *u8 = sys_mmap(256) 514 var o: i64 = 0 515 o = vi_cat(rep, o, "VIZSLA-IMPORT-LINKEDIN tenant=" as *u8); o = vi_cat(rep, o, tid) 516 o = vi_cat(rep, o, " imported=" as *u8); o = vi_catn(rep, o, imported) 517 o = vi_cat(rep, o, " skipped=" as *u8); o = vi_catn(rep, o, skipped) 518 o = vi_cat(rep, o, " (connection dates -> TOUCHes; promote close ones from network)\n" as *u8) 519 sys_write(1, rep, o) 520 return 0 521} 522 523func main(argc: i64, argv: *i64) -> i64 { 524 if argc < 2 { vi_p("VIZSLA-IMPORT usage: <vcard|linkedin> <base> <tenant-id> <file> -- fail loud\n" as *u8); return 1 } 525 let cmd: *u8 = argv[1] as *u8 526 if vi_eq(cmd, "vcard" as *u8) == 1 { return vi_cmd_vcard(argc, argv) } 527 if vi_eq(cmd, "linkedin" as *u8) == 1 { return vi_cmd_linkedin(argc, argv) } 528 vi_p("VIZSLA-IMPORT unknown command (vcard|linkedin) -- fail loud\n" as *u8) 529 return 1 530}